├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── doc ├── Makefile ├── _code │ ├── counter.py │ ├── countera.py │ ├── inline_keyboard.py │ ├── inline_per_user.py │ ├── inline_query_answerer.py │ └── inline_query_simple.py ├── conf.py ├── index.rst └── reference.rst ├── examples ├── README.md ├── callback │ ├── README.md │ ├── datecalc.py │ ├── datecalca.py │ ├── lover.py │ ├── lovera.py │ ├── quiz.py │ ├── quiza.py │ ├── vote.py │ └── votea.py ├── chat │ ├── README.md │ ├── chatbox_nodb.py │ ├── chatboxa_nodb.py │ ├── counter.py │ ├── countera.py │ ├── guess.py │ └── guessa.py ├── deeplinking │ ├── README.md │ └── flask_deeplinking.py ├── event │ ├── alarm.py │ └── alarma.py ├── inline │ ├── README.md │ ├── inline.py │ └── inlinea.py ├── payment │ ├── payment.py │ └── paymenta.py ├── simple │ ├── README.md │ ├── diceyclock.py │ ├── emodi.py │ ├── skeleton.py │ ├── skeleton_route.py │ ├── skeletona.py │ └── skeletona_route.py └── webhook │ ├── README.md │ ├── aiohttp_countera.py │ ├── aiohttp_skeletona.py │ ├── flask_counter.py │ └── flask_skeleton.py ├── setup.py ├── telepot ├── __init__.py ├── aio │ ├── __init__.py │ ├── api.py │ ├── delegate.py │ ├── hack.py │ ├── helper.py │ ├── loop.py │ └── routing.py ├── api.py ├── delegate.py ├── exception.py ├── filtering.py ├── hack.py ├── helper.py ├── loop.py ├── namedtuple.py ├── routing.py └── text.py └── test ├── bookshelf.jpg ├── dgdg.mp3 ├── document.txt ├── example.ogg ├── gandhi.png ├── hktraffic.mp4 ├── lighthouse.jpg ├── lincoln.png ├── old.cert ├── saturn.jpg ├── test27_admin.py ├── test27_inline.py ├── test27_pay.py ├── test27_queue.py ├── test27_routing.py ├── test27_send.py ├── test27_sticker.py ├── test27_text.py ├── test27_updates.py ├── test3_admin.py ├── test3_inline.py ├── test3_pay.py ├── test3_queue.py ├── test3_routing.py ├── test3_send.py ├── test3_sticker.py ├── test3_text.py ├── test3_updates.py ├── test3a_admin.py ├── test3a_inline.py ├── test3a_pay.py ├── test3a_queue.py ├── test3a_routing.py ├── test3a_send_updates.py └── test3a_sticker.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/ 3 | telepot.egg-info/ 4 | dist/ 5 | experiments/ 6 | doc/_build/ 7 | todo.txt 8 | .remote-sync.json 9 | .pypirc 10 | bugs/ 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nick Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # telepot - Python framework for Telegram Bot API 2 | 3 | ## I am no longer maintaining this library. Thanks for considering telepot. 4 | 5 | ### [Introduction »](http://telepot.readthedocs.io/en/latest/) 6 | ### [Reference »](http://telepot.readthedocs.io/en/latest/reference.html) 7 | ### [Examples »](https://github.com/nickoala/telepot/tree/master/examples) 8 | ### [Changelog »](https://github.com/nickoala/telepot/blob/master/CHANGELOG.md) 9 | -------------------------------------------------------------------------------- /doc/_code/counter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.delegate import pave_event_space, per_chat_id, create_open 6 | 7 | class MessageCounter(telepot.helper.ChatHandler): 8 | def __init__(self, *args, **kwargs): 9 | super(MessageCounter, self).__init__(*args, **kwargs) 10 | self._count = 0 11 | 12 | def on_chat_message(self, msg): 13 | self._count += 1 14 | self.sender.sendMessage(self._count) 15 | 16 | TOKEN = sys.argv[1] # get token from command-line 17 | 18 | bot = telepot.DelegatorBot(TOKEN, [ 19 | pave_event_space()( 20 | per_chat_id(), create_open, MessageCounter, timeout=10), 21 | ]) 22 | MessageLoop(bot).run_as_thread() 23 | 24 | while 1: 25 | time.sleep(10) 26 | -------------------------------------------------------------------------------- /doc/_code/countera.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import telepot 4 | from telepot.aio.loop import MessageLoop 5 | from telepot.aio.delegate import pave_event_space, per_chat_id, create_open 6 | 7 | class MessageCounter(telepot.aio.helper.ChatHandler): 8 | def __init__(self, *args, **kwargs): 9 | super(MessageCounter, self).__init__(*args, **kwargs) 10 | self._count = 0 11 | 12 | async def on_chat_message(self, msg): 13 | self._count += 1 14 | await self.sender.sendMessage(self._count) 15 | 16 | TOKEN = sys.argv[1] # get token from command-line 17 | 18 | bot = telepot.aio.DelegatorBot(TOKEN, [ 19 | pave_event_space()( 20 | per_chat_id(), create_open, MessageCounter, timeout=10), 21 | ]) 22 | 23 | loop = asyncio.get_event_loop() 24 | loop.create_task(MessageLoop(bot).run_forever()) 25 | print('Listening ...') 26 | 27 | loop.run_forever() 28 | -------------------------------------------------------------------------------- /doc/_code/inline_keyboard.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 6 | 7 | def on_chat_message(msg): 8 | content_type, chat_type, chat_id = telepot.glance(msg) 9 | 10 | keyboard = InlineKeyboardMarkup(inline_keyboard=[ 11 | [InlineKeyboardButton(text='Press me', callback_data='press')], 12 | ]) 13 | 14 | bot.sendMessage(chat_id, 'Use inline keyboard', reply_markup=keyboard) 15 | 16 | def on_callback_query(msg): 17 | query_id, from_id, query_data = telepot.glance(msg, flavor='callback_query') 18 | print('Callback Query:', query_id, from_id, query_data) 19 | 20 | bot.answerCallbackQuery(query_id, text='Got it') 21 | 22 | TOKEN = sys.argv[1] # get token from command-line 23 | 24 | bot = telepot.Bot(TOKEN) 25 | MessageLoop(bot, {'chat': on_chat_message, 26 | 'callback_query': on_callback_query}).run_as_thread() 27 | print('Listening ...') 28 | 29 | while 1: 30 | time.sleep(10) 31 | -------------------------------------------------------------------------------- /doc/_code/inline_per_user.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.delegate import pave_event_space, per_inline_from_id, create_open 6 | from telepot.namedtuple import InlineQueryResultArticle, InputTextMessageContent 7 | 8 | class QueryCounter(telepot.helper.InlineUserHandler, telepot.helper.AnswererMixin): 9 | def __init__(self, *args, **kwargs): 10 | super(QueryCounter, self).__init__(*args, **kwargs) 11 | self._count = 0 12 | 13 | def on_inline_query(self, msg): 14 | def compute(): 15 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 16 | print(self.id, ':', 'Inline Query:', query_id, from_id, query_string) 17 | 18 | self._count += 1 19 | text = '%d. %s' % (self._count, query_string) 20 | 21 | articles = [InlineQueryResultArticle( 22 | id='abc', 23 | title=text, 24 | input_message_content=InputTextMessageContent( 25 | message_text=text 26 | ) 27 | )] 28 | 29 | return articles 30 | 31 | self.answerer.answer(msg, compute) 32 | 33 | def on_chosen_inline_result(self, msg): 34 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 35 | print(self.id, ':', 'Chosen Inline Result:', result_id, from_id, query_string) 36 | 37 | TOKEN = sys.argv[1] # get token from command-line 38 | 39 | bot = telepot.DelegatorBot(TOKEN, [ 40 | pave_event_space()( 41 | per_inline_from_id(), create_open, QueryCounter, timeout=10), 42 | ]) 43 | MessageLoop(bot).run_as_thread() 44 | 45 | while 1: 46 | time.sleep(10) 47 | -------------------------------------------------------------------------------- /doc/_code/inline_query_answerer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.namedtuple import InlineQueryResultArticle, InputTextMessageContent 6 | 7 | def on_inline_query(msg): 8 | def compute(): 9 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 10 | print('Inline Query:', query_id, from_id, query_string) 11 | 12 | articles = [InlineQueryResultArticle( 13 | id='abc', 14 | title=query_string, 15 | input_message_content=InputTextMessageContent( 16 | message_text=query_string 17 | ) 18 | )] 19 | 20 | return articles 21 | 22 | answerer.answer(msg, compute) 23 | 24 | def on_chosen_inline_result(msg): 25 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 26 | print ('Chosen Inline Result:', result_id, from_id, query_string) 27 | 28 | TOKEN = sys.argv[1] # get token from command-line 29 | 30 | bot = telepot.Bot(TOKEN) 31 | answerer = telepot.helper.Answerer(bot) 32 | 33 | MessageLoop(bot, {'inline_query': on_inline_query, 34 | 'chosen_inline_result': on_chosen_inline_result}).run_as_thread() 35 | 36 | while 1: 37 | time.sleep(10) 38 | -------------------------------------------------------------------------------- /doc/_code/inline_query_simple.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.namedtuple import InlineQueryResultArticle, InputTextMessageContent 6 | 7 | def on_inline_query(msg): 8 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 9 | print ('Inline Query:', query_id, from_id, query_string) 10 | 11 | articles = [InlineQueryResultArticle( 12 | id='abc', 13 | title='ABC', 14 | input_message_content=InputTextMessageContent( 15 | message_text='Hello' 16 | ) 17 | )] 18 | 19 | bot.answerInlineQuery(query_id, articles) 20 | 21 | def on_chosen_inline_result(msg): 22 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 23 | print ('Chosen Inline Result:', result_id, from_id, query_string) 24 | 25 | TOKEN = sys.argv[1] # get token from command-line 26 | 27 | bot = telepot.Bot(TOKEN) 28 | MessageLoop(bot, {'inline_query': on_inline_query, 29 | 'chosen_inline_result': on_chosen_inline_result}).run_as_thread() 30 | 31 | while 1: 32 | time.sleep(10) 33 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | import os 5 | from datetime import datetime 6 | 7 | sys.path.insert(0, os.path.abspath('..')) 8 | 9 | extensions = [ 10 | 'sphinx.ext.autodoc', 11 | 'sphinx.ext.intersphinx', 12 | 'sphinx.ext.ifconfig', 13 | 'sphinx.ext.viewcode', 14 | ] 15 | 16 | templates_path = ['_templates'] 17 | 18 | source_suffix = '.rst' 19 | master_doc = 'index' 20 | 21 | project = u'telepot' 22 | year = datetime.now().year 23 | copyright = u'%d, Nick Lee' % year 24 | author = u'Nick Lee' 25 | 26 | 27 | from telepot import __version__ 28 | version = __version__ 29 | release = __version__ 30 | 31 | 32 | language = None 33 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 34 | pygments_style = 'sphinx' 35 | todo_include_todos = False 36 | 37 | 38 | html_theme = 'alabaster' 39 | html_theme_options = { 40 | 'description': 'Python framework for Telegram Bot API', 41 | 'show_powered_by': False, 42 | 'fixed_sidebar': True, 43 | 'github_user': 'nickoala', 44 | 'github_repo': 'telepot', 45 | 'github_type': 'star', 46 | } 47 | html_static_path = ['_static'] 48 | html_sidebars = { 49 | '**': [ 50 | 'about.html', 51 | 'navigation.html', 52 | 'relations.html', 53 | 'searchbox.html', 54 | ] 55 | } 56 | 57 | # Additional templates that should be rendered to pages, maps page names to 58 | # template names. 59 | #html_additional_pages = {} 60 | 61 | # If false, no module index is generated. 62 | #html_domain_indices = True 63 | 64 | # If false, no index is generated. 65 | #html_use_index = True 66 | 67 | # If true, the index is split into individual pages for each letter. 68 | #html_split_index = False 69 | 70 | # If true, links to the reST sources are added to the pages. 71 | #html_show_sourcelink = True 72 | 73 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 74 | #html_show_sphinx = True 75 | 76 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 77 | #html_show_copyright = True 78 | 79 | # If true, an OpenSearch description file will be output, and all pages will 80 | # contain a <link> tag referring to it. The value of this option must be the 81 | # base URL from which the finished HTML is served. 82 | #html_use_opensearch = '' 83 | 84 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 85 | #html_file_suffix = None 86 | 87 | # Language to be used for generating the HTML full-text search index. 88 | # Sphinx supports the following languages: 89 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 90 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 91 | #html_search_language = 'en' 92 | 93 | # A dictionary with options for the search language support, empty by default. 94 | # 'ja' uses this config value. 95 | # 'zh' user can custom change `jieba` dictionary path. 96 | #html_search_options = {'type': 'default'} 97 | 98 | # The name of a javascript file (relative to the configuration directory) that 99 | # implements a search results scorer. If empty, the default will be used. 100 | #html_search_scorer = 'scorer.js' 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'telepotdoc' 104 | 105 | # -- Options for LaTeX output --------------------------------------------- 106 | 107 | latex_elements = { 108 | # The paper size ('letterpaper' or 'a4paper'). 109 | #'papersize': 'letterpaper', 110 | 111 | # The font size ('10pt', '11pt' or '12pt'). 112 | #'pointsize': '10pt', 113 | 114 | # Additional stuff for the LaTeX preamble. 115 | #'preamble': '', 116 | 117 | # Latex figure (float) alignment 118 | #'figure_align': 'htbp', 119 | } 120 | 121 | # Grouping the document tree into LaTeX files. List of tuples 122 | # (source start file, target name, title, 123 | # author, documentclass [howto, manual, or own class]). 124 | latex_documents = [ 125 | (master_doc, 'telepot.tex', u'telepot Documentation', 126 | u'Nick Lee', 'manual'), 127 | ] 128 | 129 | # The name of an image file (relative to this directory) to place at the top of 130 | # the title page. 131 | #latex_logo = None 132 | 133 | # For "manual" documents, if this is true, then toplevel headings are parts, 134 | # not chapters. 135 | #latex_use_parts = False 136 | 137 | # If true, show page references after internal links. 138 | #latex_show_pagerefs = False 139 | 140 | # If true, show URL addresses after external links. 141 | #latex_show_urls = False 142 | 143 | # Documents to append as an appendix to all manuals. 144 | #latex_appendices = [] 145 | 146 | # If false, no module index is generated. 147 | #latex_domain_indices = True 148 | 149 | 150 | # -- Options for manual page output --------------------------------------- 151 | 152 | # One entry per manual page. List of tuples 153 | # (source start file, name, description, authors, manual section). 154 | man_pages = [ 155 | (master_doc, 'telepot', u'telepot Documentation', 156 | [author], 1) 157 | ] 158 | 159 | # If true, show URL addresses after external links. 160 | #man_show_urls = False 161 | 162 | 163 | # -- Options for Texinfo output ------------------------------------------- 164 | 165 | # Grouping the document tree into Texinfo files. List of tuples 166 | # (source start file, target name, title, author, 167 | # dir menu entry, description, category) 168 | texinfo_documents = [ 169 | (master_doc, 'telepot', u'telepot Documentation', 170 | author, 'telepot', 'One line description of project.', 171 | 'Miscellaneous'), 172 | ] 173 | 174 | # Documents to append as an appendix to all manuals. 175 | #texinfo_appendices = [] 176 | 177 | # If false, no module index is generated. 178 | #texinfo_domain_indices = True 179 | 180 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 181 | #texinfo_show_urls = 'footnote' 182 | 183 | # If true, do not generate a @detailmenu in the "Top" node's menu. 184 | #texinfo_no_detailmenu = False 185 | 186 | 187 | # Example configuration for intersphinx: refer to the Python standard library. 188 | intersphinx_mapping = {'https://docs.python.org/': None} 189 | 190 | autodoc_member_order = 'bysource' 191 | autoclass_content = 'both' 192 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | They are divided into 6 directories: 4 | 5 | ## Simple 6 | 7 | Beginner stuff. Suitable for personal use, single-user situations only. 8 | 9 | ## Chat 10 | 11 | Many-user, chat-only interactions. Basic use of `DelegatorBot`. 12 | 13 | ## Inline 14 | 15 | Handles inline query using `Answerer`. 16 | 17 | ## Callback 18 | 19 | Various styles of handling callback query. 20 | 21 | ## Webhook 22 | 23 | How to integrate telepot with webhook. 24 | 25 | ## Deep Linking 26 | 27 | Demonstration of deep linking. 28 | 29 | ## Payment 30 | 31 | How to handle payments. 32 | 33 | ## Event 34 | 35 | Use the built-in scheduler to schedule custom events 36 | -------------------------------------------------------------------------------- /examples/callback/README.md: -------------------------------------------------------------------------------- 1 | # Callback Query Examples 2 | 3 | ### A Bot in Love 4 | 5 | 1. Send him a message 6 | 2. He will ask you to marry him 7 | 3. He will keep asking until you say "Yes" 8 | 9 | If you are silent for 10 seconds, he will go away a little bit, but is still 10 | there waiting for you. What a sweet bot! 11 | 12 | It statically captures callback query according to the originating chat id. 13 | This is the chat-centric approach. 14 | 15 | Proposing is a private matter. This bot only works in a private chat. 16 | 17 | **[Traditional »](lover.py)** 18 | **[Async »](lovera.py)** 19 | 20 | ### Vote Counter 21 | 22 | Add the bot to a group. Then send a command `/vote` to the group. The bot will 23 | organize a ballot. 24 | 25 | When all group members have cast a vote or when time expires (30 seconds), 26 | it will announce the result. It demonstrates how to use the scheduler to 27 | generate an expiry event after a certain delay. 28 | 29 | It statically captures callback query according to the originating chat id. 30 | This is the chat-centric approach. 31 | 32 | **[Traditional »](vote.py)** 33 | **[Async »](votea.py)** 34 | 35 | ### Quiz Bot 36 | 37 | Send a chat message to the bot. It will give you a math quiz. Stay silent for 38 | 10 seconds to end the quiz. 39 | 40 | It handles callback query by their origins. All callback query originated from 41 | the same chat message will be handled by one `CallbackQueryOriginHandler`. 42 | 43 | **[Traditional »](quiz.py)** 44 | **[Async »](quiza.py)** 45 | 46 | ### Date Calculator 47 | 48 | When pondering the date of an appointment or a gathering, we usually think 49 | in terms of "this Saturday" or "next Monday", instead of the actual date. 50 | This *inline* bot helps you convert weekdays into actual dates. 51 | 52 | 1. Go to a group chat. The bot does not need to be a group member. You are 53 | going to inline-query it. 54 | 55 | 2. Type `@YourBot monday` or any weekday you have in mind. It will suggest a 56 | few dates based on it. 57 | 58 | 3. Choose a day from the returned results. The idea is to propose a date to 59 | the group. Group members have 30 seconds to vote on the proposed date. 60 | 61 | 4. After 30 seconds, the voting result is announced. 62 | 63 | This example dynamically maps callback query back to their originating 64 | `InlineUserHandler`, so you can do question-asking (sending a message containing 65 | an inline keyboard) and answer-gathering (receiving callback query) in the same 66 | object. 67 | 68 | **[Traditional »](datecalc.py)** 69 | **[Async »](datecalca.py)** 70 | -------------------------------------------------------------------------------- /examples/callback/datecalc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from datetime import datetime, timedelta 4 | from functools import reduce 5 | import telepot 6 | import telepot.helper 7 | from telepot.loop import MessageLoop 8 | from telepot.namedtuple import ( 9 | InlineQueryResultArticle, InputTextMessageContent, 10 | InlineKeyboardMarkup, InlineKeyboardButton, 11 | ReplyKeyboardMarkup, KeyboardButton) 12 | from telepot.delegate import ( 13 | per_inline_from_id, create_open, pave_event_space, 14 | intercept_callback_query_origin) 15 | 16 | """ 17 | $ python3.5 datecalc.py <token> 18 | 19 | When pondering the date of an appointment or a gathering, we usually think 20 | in terms of "this Saturday" or "next Monday", instead of the actual date. 21 | This *inline* bot helps you convert weekdays into actual dates. 22 | 23 | 1. Go to a group chat. The bot does not need to be a group member. You are 24 | going to inline-query it. 25 | 26 | 2. Type `@YourBot monday` or any weekday you have in mind. It will suggest a 27 | few dates based on it. 28 | 29 | 3. Choose a day from the returned results. The idea is to propose a date to 30 | the group. Group members have 30 seconds to vote on the proposed date. 31 | 32 | 4. After 30 seconds, the voting result is announced. 33 | 34 | This example dynamically maps callback query back to their originating 35 | `InlineUserHandler`, so you can do question-asking (sending a message containing 36 | an inline keyboard) and answer-gathering (receiving callback query) in the same 37 | object. 38 | """ 39 | 40 | user_ballots = telepot.helper.SafeDict() # thread-safe dict 41 | 42 | class DateCalculator(telepot.helper.InlineUserHandler, 43 | telepot.helper.AnswererMixin, 44 | telepot.helper.InterceptCallbackQueryMixin): 45 | def __init__(self, *args, **kwargs): 46 | super(DateCalculator, self).__init__(*args, **kwargs) 47 | 48 | global user_ballots 49 | if self.id in user_ballots: 50 | self._ballots = user_ballots[self.id] 51 | print('Ballot retrieved.') 52 | else: 53 | self._ballots = {} 54 | print('Ballot initialized.') 55 | 56 | self.router.routing_table['_expired'] = self.on__expired 57 | 58 | def on_inline_query(self, msg): 59 | def compute(): 60 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 61 | print('Inline query:', query_id, from_id, query_string) 62 | 63 | weekdays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] 64 | query_string = query_string.lower() 65 | 66 | query_weekdays = [day[0] for day in enumerate(weekdays) if day[1].startswith(query_string)] 67 | 68 | def days_to(today, target): 69 | d = target - today 70 | if d <= 0: 71 | d += 7 72 | return timedelta(days=d) 73 | 74 | today = datetime.today() 75 | deltas = [days_to(today.weekday(), target) for target in query_weekdays] 76 | 77 | def make_result(today, week_delta, day_delta): 78 | future = today + week_delta + day_delta 79 | 80 | n = 0 if future.weekday() > today.weekday() else 1 81 | n += int(week_delta.days / 7) 82 | 83 | return InlineQueryResultArticle( 84 | id=future.strftime('%Y-%m-%d'), 85 | title=('next '*n if n > 0 else 'this ') + weekdays[future.weekday()].capitalize(), 86 | input_message_content=InputTextMessageContent( 87 | message_text=future.strftime('%A, %Y-%m-%d') 88 | ), 89 | reply_markup=InlineKeyboardMarkup( 90 | inline_keyboard=[[ 91 | InlineKeyboardButton(text='Yes', callback_data='yes'), 92 | InlineKeyboardButton(text='No', callback_data='no'), 93 | ]] 94 | ) 95 | ) 96 | 97 | results = [] 98 | for i in range(0,3): 99 | weeks = timedelta(weeks=i) 100 | for d in deltas: 101 | results.append(make_result(today, weeks, d)) 102 | 103 | return results 104 | 105 | self.answerer.answer(msg, compute) 106 | 107 | def on_chosen_inline_result(self, msg): 108 | if 'inline_message_id' in msg: 109 | inline_message_id = msg['inline_message_id'] 110 | suggested_date = msg['result_id'] 111 | self._ballots[inline_message_id] = {} 112 | self.scheduler.event_later(30, ('_expired', {'seconds': 30, 113 | 'inline_message_id': inline_message_id, 114 | 'date': suggested_date})) 115 | 116 | def on_callback_query(self, msg): 117 | if 'inline_message_id' in msg: 118 | inline_message_id = msg['inline_message_id'] 119 | ballot = self._ballots[inline_message_id] 120 | 121 | query_id, from_id, query_data = telepot.glance(msg, flavor='callback_query') 122 | if from_id in ballot: 123 | self.bot.answerCallbackQuery(query_id, text='You have already voted %s' % ballot[from_id]) 124 | else: 125 | self.bot.answerCallbackQuery(query_id, text='Ok') 126 | ballot[from_id] = query_data 127 | 128 | def _count(self, ballot): 129 | yes = reduce(lambda a,b: a+(1 if b=='yes' else 0), ballot.values(), 0) 130 | no = reduce(lambda a,b: a+(1 if b=='no' else 0), ballot.values(), 0) 131 | return yes, no 132 | 133 | def on__expired(self, event): 134 | evt = telepot.peel(event) 135 | inline_message_id = evt['inline_message_id'] 136 | suggested_date = evt['date'] 137 | 138 | ballot = self._ballots[inline_message_id] 139 | text = '%s\nYes: %d\nNo: %d' % ((suggested_date,) + self._count(ballot)) 140 | 141 | editor = telepot.helper.Editor(self.bot, inline_message_id) 142 | editor.editMessageText(text=text, reply_markup=None) 143 | 144 | del self._ballots[inline_message_id] 145 | self.close() 146 | 147 | def on_close(self, ex): 148 | global user_ballots 149 | user_ballots[self.id] = self._ballots 150 | print('Closing, ballots saved.') 151 | 152 | 153 | TOKEN = sys.argv[1] 154 | 155 | bot = telepot.DelegatorBot(TOKEN, [ 156 | intercept_callback_query_origin( 157 | pave_event_space())( 158 | per_inline_from_id(), create_open, DateCalculator, timeout=10), 159 | ]) 160 | MessageLoop(bot).run_as_thread() 161 | print('Listening ...') 162 | 163 | while 1: 164 | time.sleep(10) 165 | -------------------------------------------------------------------------------- /examples/callback/datecalca.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from datetime import datetime, timedelta 4 | from functools import reduce 5 | from telepot import glance, peel 6 | import telepot.aio 7 | from telepot.aio.loop import MessageLoop 8 | from telepot.aio.helper import ( 9 | InlineUserHandler, AnswererMixin, InterceptCallbackQueryMixin, Editor) 10 | from telepot.namedtuple import ( 11 | InlineQueryResultArticle, InputTextMessageContent, 12 | InlineKeyboardMarkup, InlineKeyboardButton, 13 | ReplyKeyboardMarkup, KeyboardButton) 14 | from telepot.aio.delegate import ( 15 | per_inline_from_id, create_open, pave_event_space, 16 | intercept_callback_query_origin) 17 | 18 | """ 19 | $ python3.5 datecalca.py <token> 20 | 21 | When pondering the date of an appointment or a gathering, we usually think 22 | in terms of "this Saturday" or "next Monday", instead of the actual date. 23 | This *inline* bot helps you convert weekdays into actual dates. 24 | 25 | 1. Go to a group chat. The bot does not need to be a group member. You are 26 | going to inline-query it. 27 | 28 | 2. Type `@YourBot monday` or any weekday you have in mind. It will suggest a 29 | few dates based on it. 30 | 31 | 3. Choose a day from the returned results. The idea is to propose a date to 32 | the group. Group members have 30 seconds to vote on the proposed date. 33 | 34 | 4. After 30 seconds, the voting result is announced. 35 | 36 | This example dynamically maps callback query back to their originating 37 | `InlineUserHandler`, so you can do question-asking (sending a message containing 38 | an inline keyboard) and answer-gathering (receiving callback query) in the same 39 | object. 40 | """ 41 | 42 | user_ballots = dict() 43 | 44 | class DateCalculator(InlineUserHandler, 45 | AnswererMixin, 46 | InterceptCallbackQueryMixin): 47 | def __init__(self, *args, **kwargs): 48 | super(DateCalculator, self).__init__(*args, **kwargs) 49 | 50 | global user_ballots 51 | if self.id in user_ballots: 52 | self._ballots = user_ballots[self.id] 53 | print('Ballot retrieved.') 54 | else: 55 | self._ballots = {} 56 | print('Ballot initialized.') 57 | 58 | self.router.routing_table['_expired'] = self.on__expired 59 | 60 | def on_inline_query(self, msg): 61 | def compute(): 62 | query_id, from_id, query_string = glance(msg, flavor='inline_query') 63 | print('Inline query:', query_id, from_id, query_string) 64 | 65 | weekdays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] 66 | query_string = query_string.lower() 67 | 68 | query_weekdays = [day[0] for day in enumerate(weekdays) if day[1].startswith(query_string)] 69 | 70 | def days_to(today, target): 71 | d = target - today 72 | if d <= 0: 73 | d += 7 74 | return timedelta(days=d) 75 | 76 | today = datetime.today() 77 | deltas = [days_to(today.weekday(), target) for target in query_weekdays] 78 | 79 | def make_result(today, week_delta, day_delta): 80 | future = today + week_delta + day_delta 81 | 82 | n = 0 if future.weekday() > today.weekday() else 1 83 | n += int(week_delta.days / 7) 84 | 85 | return InlineQueryResultArticle( 86 | id=future.strftime('%Y-%m-%d'), 87 | title=('next '*n if n > 0 else 'this ') + weekdays[future.weekday()].capitalize(), 88 | input_message_content=InputTextMessageContent( 89 | message_text=future.strftime('%A, %Y-%m-%d') 90 | ), 91 | reply_markup=InlineKeyboardMarkup( 92 | inline_keyboard=[[ 93 | InlineKeyboardButton(text='Yes', callback_data='yes'), 94 | InlineKeyboardButton(text='No', callback_data='no'), 95 | ]] 96 | ) 97 | ) 98 | 99 | results = [] 100 | for i in range(0,3): 101 | weeks = timedelta(weeks=i) 102 | for d in deltas: 103 | results.append(make_result(today, weeks, d)) 104 | 105 | return results 106 | 107 | self.answerer.answer(msg, compute) 108 | 109 | def on_chosen_inline_result(self, msg): 110 | if 'inline_message_id' in msg: 111 | inline_message_id = msg['inline_message_id'] 112 | suggested_date = msg['result_id'] 113 | self._ballots[inline_message_id] = {} 114 | self.scheduler.event_later(30, ('_expired', {'seconds': 30, 115 | 'inline_message_id': inline_message_id, 116 | 'date': suggested_date})) 117 | 118 | async def on_callback_query(self, msg): 119 | if 'inline_message_id' in msg: 120 | inline_message_id = msg['inline_message_id'] 121 | ballot = self._ballots[inline_message_id] 122 | 123 | query_id, from_id, query_data = glance(msg, flavor='callback_query') 124 | if from_id in ballot: 125 | await self.bot.answerCallbackQuery(query_id, text='You have already voted %s' % ballot[from_id]) 126 | else: 127 | await self.bot.answerCallbackQuery(query_id, text='Ok') 128 | ballot[from_id] = query_data 129 | 130 | def _count(self, ballot): 131 | yes = reduce(lambda a,b: a+(1 if b=='yes' else 0), ballot.values(), 0) 132 | no = reduce(lambda a,b: a+(1 if b=='no' else 0), ballot.values(), 0) 133 | return yes, no 134 | 135 | async def on__expired(self, event): 136 | evt = peel(event) 137 | inline_message_id = evt['inline_message_id'] 138 | suggested_date = evt['date'] 139 | 140 | ballot = self._ballots[inline_message_id] 141 | text = '%s\nYes: %d\nNo: %d' % ((suggested_date,) + self._count(ballot)) 142 | 143 | editor = Editor(self.bot, inline_message_id) 144 | await editor.editMessageText(text=text, reply_markup=None) 145 | 146 | del self._ballots[inline_message_id] 147 | self.close() 148 | 149 | def on_close(self, ex): 150 | global user_ballots 151 | user_ballots[self.id] = self._ballots 152 | print('Closing, ballots saved.') 153 | 154 | 155 | TOKEN = sys.argv[1] 156 | 157 | bot = telepot.aio.DelegatorBot(TOKEN, [ 158 | intercept_callback_query_origin( 159 | pave_event_space())( 160 | per_inline_from_id(), create_open, DateCalculator, timeout=10), 161 | ]) 162 | 163 | loop = asyncio.get_event_loop() 164 | loop.create_task(MessageLoop(bot).run_forever()) 165 | print('Listening ...') 166 | 167 | loop.run_forever() 168 | -------------------------------------------------------------------------------- /examples/callback/lover.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | import telepot.helper 5 | from telepot.loop import MessageLoop 6 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 7 | from telepot.delegate import ( 8 | per_chat_id, create_open, pave_event_space, include_callback_query_chat_id) 9 | 10 | """ 11 | $ python3.5 lover.py <token> 12 | 13 | 1. Send him a message 14 | 2. He will ask you to marry him 15 | 3. He will keep asking until you say "Yes" 16 | 17 | If you are silent for 10 seconds, he will go away a little bit, but is still 18 | there waiting for you. What a sweet bot! 19 | 20 | It statically captures callback query according to the originating chat id. 21 | This is the chat-centric approach. 22 | 23 | Proposing is a private matter. This bot only works in a private chat. 24 | """ 25 | 26 | propose_records = telepot.helper.SafeDict() # thread-safe dict 27 | 28 | class Lover(telepot.helper.ChatHandler): 29 | keyboard = InlineKeyboardMarkup(inline_keyboard=[[ 30 | InlineKeyboardButton(text='Yes', callback_data='yes'), 31 | InlineKeyboardButton(text='um ...', callback_data='no'), 32 | ]]) 33 | 34 | def __init__(self, *args, **kwargs): 35 | super(Lover, self).__init__(*args, **kwargs) 36 | 37 | # Retrieve from database 38 | global propose_records 39 | if self.id in propose_records: 40 | self._count, self._edit_msg_ident = propose_records[self.id] 41 | self._editor = telepot.helper.Editor(self.bot, self._edit_msg_ident) if self._edit_msg_ident else None 42 | else: 43 | self._count = 0 44 | self._edit_msg_ident = None 45 | self._editor = None 46 | 47 | def _propose(self): 48 | self._count += 1 49 | sent = self.sender.sendMessage('%d. Would you marry me?' % self._count, reply_markup=self.keyboard) 50 | self._editor = telepot.helper.Editor(self.bot, sent) 51 | self._edit_msg_ident = telepot.message_identifier(sent) 52 | 53 | def _cancel_last(self): 54 | if self._editor: 55 | self._editor.editMessageReplyMarkup(reply_markup=None) 56 | self._editor = None 57 | self._edit_msg_ident = None 58 | 59 | def on_chat_message(self, msg): 60 | self._propose() 61 | 62 | def on_callback_query(self, msg): 63 | query_id, from_id, query_data = telepot.glance(msg, flavor='callback_query') 64 | 65 | if query_data == 'yes': 66 | self._cancel_last() 67 | self.sender.sendMessage('Thank you!') 68 | self.close() 69 | else: 70 | self.bot.answerCallbackQuery(query_id, text='Ok. But I am going to keep asking.') 71 | self._cancel_last() 72 | self._propose() 73 | 74 | def on__idle(self, event): 75 | self.sender.sendMessage('I know you may need a little time. I will always be here for you.') 76 | self.close() 77 | 78 | def on_close(self, ex): 79 | # Save to database 80 | global propose_records 81 | propose_records[self.id] = (self._count, self._edit_msg_ident) 82 | 83 | 84 | TOKEN = sys.argv[1] 85 | 86 | bot = telepot.DelegatorBot(TOKEN, [ 87 | include_callback_query_chat_id( 88 | pave_event_space())( 89 | per_chat_id(types=['private']), create_open, Lover, timeout=10), 90 | ]) 91 | MessageLoop(bot).run_as_thread() 92 | print('Listening ...') 93 | 94 | while 1: 95 | time.sleep(10) 96 | -------------------------------------------------------------------------------- /examples/callback/lovera.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from telepot import message_identifier, glance 4 | import telepot.aio 5 | import telepot.aio.helper 6 | from telepot.aio.loop import MessageLoop 7 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 8 | from telepot.aio.delegate import ( 9 | per_chat_id, create_open, pave_event_space, include_callback_query_chat_id) 10 | 11 | """ 12 | $ python3.5 lovera.py <token> 13 | 14 | 1. Send him a message 15 | 2. He will ask you to marry him 16 | 3. He will keep asking until you say "Yes" 17 | 18 | If you are silent for 10 seconds, he will go away a little bit, but is still 19 | there waiting for you. What a sweet bot! 20 | 21 | It statically captures callback query according to the originating chat id. 22 | This is the chat-centric approach. 23 | 24 | Proposing is a private matter. This bot only works in a private chat. 25 | """ 26 | 27 | propose_records = dict() 28 | 29 | class Lover(telepot.aio.helper.ChatHandler): 30 | keyboard = InlineKeyboardMarkup(inline_keyboard=[[ 31 | InlineKeyboardButton(text='Yes', callback_data='yes'), 32 | InlineKeyboardButton(text='um ...', callback_data='no'), 33 | ]]) 34 | 35 | def __init__(self, *args, **kwargs): 36 | super(Lover, self).__init__(*args, **kwargs) 37 | 38 | # Retrieve from database 39 | global propose_records 40 | if self.id in propose_records: 41 | self._count, self._edit_msg_ident = propose_records[self.id] 42 | self._editor = telepot.aio.helper.Editor(self.bot, self._edit_msg_ident) if self._edit_msg_ident else None 43 | else: 44 | self._count = 0 45 | self._edit_msg_ident = None 46 | self._editor = None 47 | 48 | async def _propose(self): 49 | self._count += 1 50 | sent = await self.sender.sendMessage('%d. Would you marry me?' % self._count, reply_markup=self.keyboard) 51 | self._editor = telepot.aio.helper.Editor(self.bot, sent) 52 | self._edit_msg_ident = message_identifier(sent) 53 | 54 | async def _cancel_last(self): 55 | if self._editor: 56 | await self._editor.editMessageReplyMarkup(reply_markup=None) 57 | self._editor = None 58 | self._edit_msg_ident = None 59 | 60 | async def on_chat_message(self, msg): 61 | await self._propose() 62 | 63 | async def on_callback_query(self, msg): 64 | query_id, from_id, query_data = glance(msg, flavor='callback_query') 65 | 66 | if query_data == 'yes': 67 | await self._cancel_last() 68 | await self.sender.sendMessage('Thank you!') 69 | self.close() 70 | else: 71 | await self.bot.answerCallbackQuery(query_id, text='Ok. But I am going to keep asking.') 72 | await self._cancel_last() 73 | await self._propose() 74 | 75 | async def on__idle(self, event): 76 | await self.sender.sendMessage('I know you may need a little time. I will always be here for you.') 77 | self.close() 78 | 79 | def on_close(self, ex): 80 | # Save to database 81 | global propose_records 82 | propose_records[self.id] = (self._count, self._edit_msg_ident) 83 | 84 | 85 | TOKEN = sys.argv[1] 86 | 87 | bot = telepot.aio.DelegatorBot(TOKEN, [ 88 | include_callback_query_chat_id( 89 | pave_event_space())( 90 | per_chat_id(types=['private']), create_open, Lover, timeout=10), 91 | ]) 92 | 93 | loop = asyncio.get_event_loop() 94 | loop.create_task(MessageLoop(bot).run_forever()) 95 | print('Listening ...') 96 | 97 | loop.run_forever() 98 | -------------------------------------------------------------------------------- /examples/callback/quiz.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import random 4 | import telepot 5 | import telepot.helper 6 | from telepot.loop import MessageLoop 7 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 8 | from telepot.delegate import ( 9 | per_chat_id, per_callback_query_origin, create_open, pave_event_space) 10 | 11 | """ 12 | $ python3.5 quiz.py <token> 13 | 14 | Send a chat message to the bot. It will give you a math quiz. Stay silent for 15 | 10 seconds to end the quiz. 16 | 17 | It handles callback query by their origins. All callback query originated from 18 | the same chat message will be handled by the same `CallbackQueryOriginHandler`. 19 | """ 20 | 21 | class QuizStarter(telepot.helper.ChatHandler): 22 | def __init__(self, *args, **kwargs): 23 | super(QuizStarter, self).__init__(*args, **kwargs) 24 | 25 | def on_chat_message(self, msg): 26 | content_type, chat_type, chat_id = telepot.glance(msg) 27 | self.sender.sendMessage( 28 | 'Press START to do some math ...', 29 | reply_markup=InlineKeyboardMarkup( 30 | inline_keyboard=[[ 31 | InlineKeyboardButton(text='START', callback_data='start'), 32 | ]] 33 | ) 34 | ) 35 | self.close() # let Quizzer take over 36 | 37 | class Quizzer(telepot.helper.CallbackQueryOriginHandler): 38 | def __init__(self, *args, **kwargs): 39 | super(Quizzer, self).__init__(*args, **kwargs) 40 | self._score = {True: 0, False: 0} 41 | self._answer = None 42 | 43 | def _show_next_question(self): 44 | x = random.randint(1,50) 45 | y = random.randint(1,50) 46 | sign, op = random.choice([('+', lambda a,b: a+b), 47 | ('-', lambda a,b: a-b), 48 | ('x', lambda a,b: a*b)]) 49 | answer = op(x,y) 50 | question = '%d %s %d = ?' % (x, sign, y) 51 | choices = sorted(list(map(random.randint, [-49]*4, [2500]*4)) + [answer]) 52 | 53 | self.editor.editMessageText(question, 54 | reply_markup=InlineKeyboardMarkup( 55 | inline_keyboard=[ 56 | list(map(lambda c: InlineKeyboardButton(text=str(c), callback_data=str(c)), choices)) 57 | ] 58 | ) 59 | ) 60 | return answer 61 | 62 | def on_callback_query(self, msg): 63 | query_id, from_id, query_data = telepot.glance(msg, flavor='callback_query') 64 | 65 | if query_data != 'start': 66 | self._score[self._answer == int(query_data)] += 1 67 | 68 | self._answer = self._show_next_question() 69 | 70 | def on__idle(self, event): 71 | text = '%d out of %d' % (self._score[True], self._score[True]+self._score[False]) 72 | self.editor.editMessageText( 73 | text + '\n\nThis message will disappear in 5 seconds to test deleteMessage', 74 | reply_markup=None) 75 | 76 | time.sleep(5) 77 | self.editor.deleteMessage() 78 | self.close() 79 | 80 | 81 | TOKEN = sys.argv[1] 82 | 83 | bot = telepot.DelegatorBot(TOKEN, [ 84 | pave_event_space()( 85 | per_chat_id(), create_open, QuizStarter, timeout=3), 86 | pave_event_space()( 87 | per_callback_query_origin(), create_open, Quizzer, timeout=10), 88 | ]) 89 | 90 | MessageLoop(bot).run_as_thread() 91 | print('Listening ...') 92 | 93 | while 1: 94 | time.sleep(10) 95 | -------------------------------------------------------------------------------- /examples/callback/quiza.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import random 4 | from telepot import glance 5 | import telepot.aio 6 | import telepot.aio.helper 7 | from telepot.aio.loop import MessageLoop 8 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 9 | from telepot.aio.delegate import ( 10 | per_chat_id, per_callback_query_origin, create_open, pave_event_space) 11 | 12 | """ 13 | $ python3.5 quiza.py <token> 14 | 15 | Send a chat message to the bot. It will give you a math quiz. Stay silent for 16 | 10 seconds to end the quiz. 17 | 18 | It handles callback query by their origins. All callback query originated from 19 | the same chat message will be handled by the same `CallbackQueryOriginHandler`. 20 | """ 21 | 22 | class QuizStarter(telepot.aio.helper.ChatHandler): 23 | def __init__(self, *args, **kwargs): 24 | super(QuizStarter, self).__init__(*args, **kwargs) 25 | 26 | async def on_chat_message(self, msg): 27 | content_type, chat_type, chat_id = glance(msg) 28 | await self.sender.sendMessage( 29 | 'Press START to do some math ...', 30 | reply_markup=InlineKeyboardMarkup( 31 | inline_keyboard=[[ 32 | InlineKeyboardButton(text='START', callback_data='start'), 33 | ]] 34 | ) 35 | ) 36 | self.close() # let Quizzer take over 37 | 38 | class Quizzer(telepot.aio.helper.CallbackQueryOriginHandler): 39 | def __init__(self, *args, **kwargs): 40 | super(Quizzer, self).__init__(*args, **kwargs) 41 | self._score = {True: 0, False: 0} 42 | self._answer = None 43 | 44 | async def _show_next_question(self): 45 | x = random.randint(1,50) 46 | y = random.randint(1,50) 47 | sign, op = random.choice([('+', lambda a,b: a+b), 48 | ('-', lambda a,b: a-b), 49 | ('x', lambda a,b: a*b)]) 50 | answer = op(x,y) 51 | question = '%d %s %d = ?' % (x, sign, y) 52 | choices = sorted(list(map(random.randint, [-49]*4, [2500]*4)) + [answer]) 53 | 54 | await self.editor.editMessageText(question, 55 | reply_markup=InlineKeyboardMarkup( 56 | inline_keyboard=[ 57 | list(map(lambda c: InlineKeyboardButton(text=str(c), callback_data=str(c)), choices)) 58 | ] 59 | ) 60 | ) 61 | return answer 62 | 63 | async def on_callback_query(self, msg): 64 | query_id, from_id, query_data = glance(msg, flavor='callback_query') 65 | 66 | if query_data != 'start': 67 | self._score[self._answer == int(query_data)] += 1 68 | 69 | self._answer = await self._show_next_question() 70 | 71 | async def on__idle(self, event): 72 | text = '%d out of %d' % (self._score[True], self._score[True]+self._score[False]) 73 | await self.editor.editMessageText( 74 | text + '\n\nThis message will disappear in 5 seconds to test deleteMessage', 75 | reply_markup=None) 76 | 77 | await asyncio.sleep(5) 78 | await self.editor.deleteMessage() 79 | self.close() 80 | 81 | 82 | TOKEN = sys.argv[1] 83 | 84 | bot = telepot.aio.DelegatorBot(TOKEN, [ 85 | pave_event_space()( 86 | per_chat_id(), create_open, QuizStarter, timeout=3), 87 | pave_event_space()( 88 | per_callback_query_origin(), create_open, Quizzer, timeout=10), 89 | ]) 90 | 91 | loop = asyncio.get_event_loop() 92 | loop.create_task(MessageLoop(bot).run_forever()) 93 | print('Listening ...') 94 | 95 | loop.run_forever() 96 | -------------------------------------------------------------------------------- /examples/callback/vote.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from functools import reduce 4 | import telepot 5 | import telepot.helper 6 | from telepot.loop import MessageLoop 7 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 8 | from telepot.delegate import ( 9 | per_chat_id, create_open, pave_event_space, include_callback_query_chat_id) 10 | 11 | """ 12 | $ python3.5 vote.py <token> 13 | 14 | Add the bot to a group. Then send a command `/vote` to the group. The bot will 15 | organize a ballot. 16 | 17 | When all group members have cast a vote or when time expires (30 seconds), 18 | it will announce the result. It demonstrates how to use the scheduler to 19 | generate an expiry event after a certain delay. 20 | 21 | It statically captures callback query according to the originating chat id. 22 | This is the chat-centric approach. 23 | """ 24 | 25 | votes = telepot.helper.SafeDict() # thread-safe dict 26 | 27 | class VoteCounter(telepot.helper.ChatHandler): 28 | def __init__(self, *args, **kwargs): 29 | super(VoteCounter, self).__init__(*args, **kwargs) 30 | 31 | global votes 32 | if self.id in votes: 33 | self._ballot_box, self._keyboard_msg_ident, self._expired_event = votes[self.id] 34 | self._editor = telepot.helper.Editor(self.bot, self._keyboard_msg_ident) if self._keyboard_msg_ident else None 35 | else: 36 | self._ballot_box = None 37 | self._keyboard_msg_ident = None 38 | self._editor = None 39 | self._expired_event = None 40 | 41 | self._member_count = self.administrator.getChatMembersCount() - 1 # exclude myself, the bot 42 | 43 | # Catch _vote_expired event 44 | self.router.routing_table['_vote_expired'] = self.on__vote_expired 45 | 46 | def on_chat_message(self, msg): 47 | content_type, chat_type, chat_id = telepot.glance(msg) 48 | 49 | if content_type != 'text': 50 | print('Not a text message.') 51 | return 52 | 53 | if msg['text'] != '/vote': 54 | print('Not /vote') 55 | return 56 | 57 | if self._ballot_box is not None: 58 | self.sender.sendMessage('Voting still in progress') 59 | else: 60 | self._init_ballot() 61 | 62 | def _count_votes(self): 63 | yes = reduce(lambda a,b: a+(1 if b=='yes' else 0), self._ballot_box.values(), 0) 64 | no = reduce(lambda a,b: a+(1 if b=='no' else 0), self._ballot_box.values(), 0) 65 | return yes, no, self._member_count-yes-no 66 | 67 | def _init_ballot(self): 68 | keyboard = InlineKeyboardMarkup(inline_keyboard=[[ 69 | InlineKeyboardButton(text='Yes', callback_data='yes'), 70 | InlineKeyboardButton(text='Nah!!!!', callback_data='no'), 71 | ]]) 72 | sent = self.sender.sendMessage("Let's Vote ...", reply_markup=keyboard) 73 | 74 | self._ballot_box = {} 75 | self._keyboard_msg_ident = telepot.message_identifier(sent) 76 | self._editor = telepot.helper.Editor(self.bot, self._keyboard_msg_ident) 77 | 78 | # Generate an expiry event 30 seconds later 79 | self._expired_event = self.scheduler.event_later(30, ('_vote_expired', {'seconds': 30})) 80 | 81 | def _close_ballot(self): 82 | try: 83 | self.scheduler.cancel(self._expired_event) 84 | # The expiry event may have already occurred and cannot be found in scheduler. 85 | except telepot.exception.EventNotFound: 86 | pass 87 | 88 | self._editor.editMessageReplyMarkup(reply_markup=None) 89 | 90 | self._ballot_box = None 91 | self._keyboard_msg_ident = None 92 | self._editor = None 93 | 94 | def on_callback_query(self, msg): 95 | query_id, from_id, query_data = telepot.glance(msg, flavor='callback_query') 96 | 97 | if from_id in self._ballot_box: 98 | self.bot.answerCallbackQuery(query_id, text='You have already voted %s' % self._ballot_box[from_id]) 99 | else: 100 | self.bot.answerCallbackQuery(query_id, text='Ok') 101 | self._ballot_box[from_id] = query_data 102 | 103 | # Announce results if everyone has voted. 104 | if len(self._ballot_box) >= self._member_count: 105 | result = self._count_votes() 106 | self._close_ballot() 107 | self.sender.sendMessage('Everyone has voted:\nYes: %d\nNo: %d\nSilent: %d' % result) 108 | 109 | def on__vote_expired(self, event): 110 | result = self._count_votes() 111 | self._close_ballot() 112 | self.sender.sendMessage('Time is up:\nYes: %d\nNo: %d\nSilent: %d' % result) 113 | 114 | def on_close(self, ex): 115 | global votes 116 | if self._ballot_box is None: 117 | try: 118 | del votes[self.id] 119 | except KeyError: 120 | pass 121 | else: 122 | votes[self.id] = (self._ballot_box, self._keyboard_msg_ident, self._expired_event) 123 | 124 | from pprint import pprint 125 | pprint(votes) 126 | 127 | 128 | TOKEN = sys.argv[1] 129 | 130 | bot = telepot.DelegatorBot(TOKEN, [ 131 | include_callback_query_chat_id( 132 | pave_event_space())( 133 | per_chat_id(types=['group']), create_open, VoteCounter, timeout=10), 134 | ]) 135 | MessageLoop(bot).run_as_thread() 136 | print('Listening ...') 137 | 138 | while 1: 139 | time.sleep(10) 140 | -------------------------------------------------------------------------------- /examples/callback/votea.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from functools import reduce 4 | from telepot import glance, message_identifier 5 | import telepot.aio 6 | import telepot.aio.helper 7 | from telepot.aio.loop import MessageLoop 8 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 9 | from telepot.aio.delegate import ( 10 | per_chat_id, create_open, pave_event_space, include_callback_query_chat_id) 11 | 12 | """ 13 | $ python3.5 votea.py <token> 14 | 15 | Add the bot to a group. Then send a command `/vote` to the group. The bot will 16 | organize a ballot. 17 | 18 | When all group members have cast a vote or when time expires (30 seconds), 19 | it will announce the result. It demonstrates how to use the scheduler to 20 | generate an expiry event after a certain delay. 21 | 22 | It statically captures callback query according to the originating chat id. 23 | This is the chat-centric approach. 24 | """ 25 | 26 | votes = dict() 27 | 28 | class VoteCounter(telepot.aio.helper.ChatHandler): 29 | def __init__(self, *args, **kwargs): 30 | super(VoteCounter, self).__init__(*args, **kwargs) 31 | 32 | global votes 33 | if self.id in votes: 34 | self._ballot_box, self._keyboard_msg_ident, self._expired_event, self._member_count = votes[self.id] 35 | self._editor = telepot.aio.helper.Editor(self.bot, self._keyboard_msg_ident) if self._keyboard_msg_ident else None 36 | else: 37 | self._ballot_box = None 38 | self._keyboard_msg_ident = None 39 | self._editor = None 40 | self._expired_event = None 41 | self._member_count = None 42 | 43 | # Catch _vote_expired event 44 | self.router.routing_table['_vote_expired'] = self.on__vote_expired 45 | 46 | async def on_chat_message(self, msg): 47 | content_type, chat_type, chat_id = glance(msg) 48 | 49 | if content_type != 'text': 50 | print('Not a text message.') 51 | return 52 | 53 | if msg['text'] != '/vote': 54 | print('Not /vote') 55 | return 56 | 57 | if self._ballot_box is not None: 58 | await self.sender.sendMessage('Voting still in progress') 59 | else: 60 | await self._init_ballot() 61 | 62 | def _count_votes(self): 63 | yes = reduce(lambda a,b: a+(1 if b=='yes' else 0), self._ballot_box.values(), 0) 64 | no = reduce(lambda a,b: a+(1 if b=='no' else 0), self._ballot_box.values(), 0) 65 | return yes, no, self._member_count-yes-no 66 | 67 | async def _init_ballot(self): 68 | keyboard = InlineKeyboardMarkup(inline_keyboard=[[ 69 | InlineKeyboardButton(text='Yes', callback_data='yes'), 70 | InlineKeyboardButton(text='Nah!!!!', callback_data='no'), 71 | ]]) 72 | sent = await self.sender.sendMessage("Let's Vote ...", reply_markup=keyboard) 73 | 74 | self._member_count = await self.administrator.getChatMembersCount() - 1 # exclude myself, the bot 75 | 76 | self._ballot_box = {} 77 | self._keyboard_msg_ident = message_identifier(sent) 78 | self._editor = telepot.aio.helper.Editor(self.bot, self._keyboard_msg_ident) 79 | 80 | # Generate an expiry event 30 seconds later 81 | self._expired_event = self.scheduler.event_later(30, ('_vote_expired', {'seconds': 30})) 82 | 83 | async def _close_ballot(self): 84 | self.scheduler.cancel(self._expired_event) 85 | 86 | await self._editor.editMessageReplyMarkup(reply_markup=None) 87 | 88 | self._ballot_box = None 89 | self._keyboard_msg_ident = None 90 | self._editor = None 91 | 92 | async def on_callback_query(self, msg): 93 | query_id, from_id, query_data = glance(msg, flavor='callback_query') 94 | 95 | if from_id in self._ballot_box: 96 | await self.bot.answerCallbackQuery(query_id, text='You have already voted %s' % self._ballot_box[from_id]) 97 | else: 98 | await self.bot.answerCallbackQuery(query_id, text='Ok') 99 | self._ballot_box[from_id] = query_data 100 | 101 | # Announce results if everyone has voted. 102 | if len(self._ballot_box) >= self._member_count: 103 | result = self._count_votes() 104 | await self._close_ballot() 105 | await self.sender.sendMessage('Everyone has voted:\nYes: %d\nNo: %d\nSilent: %d' % result) 106 | 107 | async def on__vote_expired(self, event): 108 | result = self._count_votes() 109 | await self._close_ballot() 110 | await self.sender.sendMessage('Time is up:\nYes: %d\nNo: %d\nSilent: %d' % result) 111 | 112 | def on_close(self, ex): 113 | global votes 114 | if self._ballot_box is None: 115 | try: 116 | del votes[self.id] 117 | except KeyError: 118 | pass 119 | else: 120 | votes[self.id] = (self._ballot_box, self._keyboard_msg_ident, self._expired_event, self._member_count) 121 | 122 | from pprint import pprint 123 | print('%d closing ...' % self.id) 124 | pprint(votes) 125 | 126 | 127 | TOKEN = sys.argv[1] 128 | 129 | bot = telepot.aio.DelegatorBot(TOKEN, [ 130 | include_callback_query_chat_id( 131 | pave_event_space())( 132 | per_chat_id(types=['group']), create_open, VoteCounter, timeout=10), 133 | ]) 134 | 135 | loop = asyncio.get_event_loop() 136 | loop.create_task(MessageLoop(bot).run_forever()) 137 | print('Listening ...') 138 | 139 | loop.run_forever() 140 | -------------------------------------------------------------------------------- /examples/chat/README.md: -------------------------------------------------------------------------------- 1 | # Chat Examples 2 | 3 | ### Message Counter 4 | 5 | Counts number of messages a user has sent. Starts over if silent for 10 seconds. 6 | Illustrates the basic usage of `DelegateBot` and `ChatHandler`. 7 | 8 | **[Traditional »](counter.py)** 9 | **[Async »](countera.py)** 10 | 11 | ### Guess a number 12 | 13 | 1. Send the bot anything to start a game. 14 | 2. The bot randomly picks an integer between 0-99. 15 | 3. You make a guess. 16 | 4. The bot tells you to go higher or lower. 17 | 5. Repeat step 3 and 4, until guess is correct. 18 | 19 | **[Traditional »](guess.py)** 20 | **[Async »](guessa.py)** 21 | 22 | ### Chatbox - a Mailbox for Chats 23 | 24 | 1. People send messages to your bot. 25 | 2. Your bot remembers the messages. 26 | 3. You read the messages later. 27 | 28 | It accepts the following commands from you, the owner, only: 29 | 30 | - `/unread` - tells you who has sent you messages and how many 31 | - `/next` - read next sender's messages 32 | 33 | This example can be a starting point for **customer support** type of bots. 34 | For example, customers send questions to a bot account; staff answers questions 35 | behind the scene, makes it look like the bot is answering questions. 36 | 37 | It further illustrates the use of `DelegateBot` and `ChatHandler`, and how to 38 | spawn delegates differently according to the role of users. 39 | 40 | This example only handles text messages and stores messages in memory. 41 | If the bot is killed, all messages are lost. It is an *example* after all. 42 | 43 | **[Traditional »](chatbox_nodb.py)** 44 | **[Async »](chatboxa_nodb.py)** 45 | -------------------------------------------------------------------------------- /examples/chat/chatboxa_nodb.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import telepot 4 | from telepot.aio.loop import MessageLoop 5 | from telepot.aio.delegate import ( 6 | per_chat_id_in, per_application, call, create_open, pave_event_space) 7 | 8 | """ 9 | $ python3.5 chatboxa_nodb.py <token> <owner_id> 10 | 11 | Chatbox - a mailbox for chats 12 | 13 | 1. People send messages to your bot. 14 | 2. Your bot remembers the messages. 15 | 3. You read the messages later. 16 | 17 | It accepts the following commands from you, the owner, only: 18 | 19 | - `/unread` - tells you who has sent you messages and how many 20 | - `/next` - read next sender's messages 21 | 22 | This example can be a starting point for **customer support** type of bots. 23 | For example, customers send questions to a bot account; staff answers questions 24 | behind the scene, makes it look like the bot is answering questions. 25 | 26 | It further illustrates the use of `DelegateBot` and `ChatHandler`, and how to 27 | spawn delegates differently according to the role of users. 28 | 29 | This example only handles text messages and stores messages in memory. 30 | If the bot is killed, all messages are lost. It is an *example* after all. 31 | """ 32 | 33 | # Simulate a database to store unread messages 34 | class UnreadStore(object): 35 | def __init__(self): 36 | self._db = {} 37 | 38 | def put(self, msg): 39 | chat_id = msg['chat']['id'] 40 | 41 | if chat_id not in self._db: 42 | self._db[chat_id] = [] 43 | 44 | self._db[chat_id].append(msg) 45 | 46 | # Pull all unread messages of a `chat_id` 47 | def pull(self, chat_id): 48 | messages = self._db[chat_id] 49 | del self._db[chat_id] 50 | 51 | # sort by date 52 | messages.sort(key=lambda m: m['date']) 53 | return messages 54 | 55 | # Tells how many unread messages per chat_id 56 | def unread_per_chat(self): 57 | return [(k,len(v)) for k,v in self._db.items()] 58 | 59 | 60 | # Accept commands from owner. Give him unread messages. 61 | class OwnerHandler(telepot.aio.helper.ChatHandler): 62 | def __init__(self, seed_tuple, store, **kwargs): 63 | super(OwnerHandler, self).__init__(seed_tuple, **kwargs) 64 | self._store = store 65 | 66 | async def _read_messages(self, messages): 67 | for msg in messages: 68 | # assume all messages are text 69 | await self.sender.sendMessage(msg['text']) 70 | 71 | async def on_chat_message(self, msg): 72 | content_type, chat_type, chat_id = telepot.glance(msg) 73 | 74 | if content_type != 'text': 75 | await self.sender.sendMessage("I don't understand") 76 | return 77 | 78 | command = msg['text'].strip().lower() 79 | 80 | # Tells who has sent you how many messages 81 | if command == '/unread': 82 | results = self._store.unread_per_chat() 83 | 84 | lines = [] 85 | for r in results: 86 | n = 'ID: %d\n%d unread' % r 87 | lines.append(n) 88 | 89 | if not len(lines): 90 | await self.sender.sendMessage('No unread messages') 91 | else: 92 | await self.sender.sendMessage('\n'.join(lines)) 93 | 94 | # read next sender's messages 95 | elif command == '/next': 96 | results = self._store.unread_per_chat() 97 | 98 | if not len(results): 99 | await self.sender.sendMessage('No unread messages') 100 | return 101 | 102 | chat_id = results[0][0] 103 | unread_messages = self._store.pull(chat_id) 104 | 105 | await self.sender.sendMessage('From ID: %d' % chat_id) 106 | await self._read_messages(unread_messages) 107 | 108 | else: 109 | await self.sender.sendMessage("I don't understand") 110 | 111 | 112 | class MessageSaver(telepot.aio.helper.Monitor): 113 | def __init__(self, seed_tuple, store, exclude): 114 | # The `capture` criteria means to capture all messages. 115 | super(MessageSaver, self).__init__(seed_tuple, capture=[[lambda msg: not telepot.is_event(msg)]]) 116 | self._store = store 117 | self._exclude = exclude 118 | 119 | # Store every message, except those whose sender is in the exclude list, or non-text messages. 120 | def on_chat_message(self, msg): 121 | content_type, chat_type, chat_id = telepot.glance(msg) 122 | 123 | if chat_id in self._exclude: 124 | print('Chat id %d is excluded.' % chat_id) 125 | return 126 | 127 | if content_type != 'text': 128 | print('Content type %s is ignored.' % content_type) 129 | return 130 | 131 | print('Storing message: %s' % msg) 132 | self._store.put(msg) 133 | 134 | 135 | class ChatBox(telepot.aio.DelegatorBot): 136 | def __init__(self, token, owner_id): 137 | self._owner_id = owner_id 138 | self._seen = set() 139 | self._store = UnreadStore() 140 | 141 | super(ChatBox, self).__init__(token, [ 142 | # Here is a delegate to specially handle owner commands. 143 | pave_event_space()( 144 | per_chat_id_in([owner_id]), create_open, OwnerHandler, self._store, timeout=20), 145 | 146 | # Only one MessageSaver is ever spawned for entire application. 147 | (per_application(), create_open(MessageSaver, self._store, exclude=[owner_id])), 148 | 149 | # For senders never seen before, send him a welcome message. 150 | (self._is_newcomer, call(self._send_welcome)), 151 | ]) 152 | 153 | # seed-calculating function: use returned value to indicate whether to spawn a delegate 154 | def _is_newcomer(self, msg): 155 | if telepot.is_event(msg): 156 | return None 157 | 158 | chat_id = msg['chat']['id'] 159 | if chat_id == self._owner_id: # Sender is owner 160 | return None # No delegate spawned 161 | 162 | if chat_id in self._seen: # Sender has been seen before 163 | return None # No delegate spawned 164 | 165 | self._seen.add(chat_id) 166 | return [] # non-hashable ==> delegates are independent, no seed association is made. 167 | 168 | async def _send_welcome(self, seed_tuple): 169 | chat_id = seed_tuple[1]['chat']['id'] 170 | 171 | print('Sending welcome ...') 172 | await self.sendMessage(chat_id, 'Hello!') 173 | 174 | 175 | TOKEN = sys.argv[1] 176 | OWNER_ID = int(sys.argv[2]) 177 | 178 | bot = ChatBox(TOKEN, OWNER_ID) 179 | loop = asyncio.get_event_loop() 180 | 181 | loop.create_task(MessageLoop(bot).run_forever()) 182 | print('Listening ...') 183 | 184 | loop.run_forever() 185 | -------------------------------------------------------------------------------- /examples/chat/counter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.delegate import per_chat_id, create_open, pave_event_space 6 | 7 | """ 8 | $ python2.7 counter.py <token> 9 | 10 | Counts number of messages a user has sent. Starts over if silent for 10 seconds. 11 | Illustrates the basic usage of `DelegateBot` and `ChatHandler`. 12 | """ 13 | 14 | class MessageCounter(telepot.helper.ChatHandler): 15 | def __init__(self, *args, **kwargs): 16 | super(MessageCounter, self).__init__(*args, **kwargs) 17 | self._count = 0 18 | 19 | def on_chat_message(self, msg): 20 | self._count += 1 21 | self.sender.sendMessage(self._count) 22 | 23 | 24 | TOKEN = sys.argv[1] # get token from command-line 25 | 26 | bot = telepot.DelegatorBot(TOKEN, [ 27 | pave_event_space()( 28 | per_chat_id(), create_open, MessageCounter, timeout=10 29 | ), 30 | ]) 31 | MessageLoop(bot).run_as_thread() 32 | print('Listening ...') 33 | 34 | while 1: 35 | time.sleep(10) 36 | -------------------------------------------------------------------------------- /examples/chat/countera.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import telepot 4 | from telepot.aio.loop import MessageLoop 5 | from telepot.aio.delegate import per_chat_id, create_open, pave_event_space 6 | 7 | """ 8 | $ python3.5 countera.py <token> 9 | 10 | Counts number of messages a user has sent. Starts over if silent for 10 seconds. 11 | Illustrates the basic usage of `DelegateBot` and `ChatHandler`. 12 | """ 13 | 14 | class MessageCounter(telepot.aio.helper.ChatHandler): 15 | def __init__(self, *args, **kwargs): 16 | super(MessageCounter, self).__init__(*args, **kwargs) 17 | self._count = 0 18 | 19 | async def on_chat_message(self, msg): 20 | self._count += 1 21 | await self.sender.sendMessage(self._count) 22 | 23 | 24 | TOKEN = sys.argv[1] # get token from command-line 25 | 26 | bot = telepot.aio.DelegatorBot(TOKEN, [ 27 | pave_event_space()( 28 | per_chat_id(), create_open, MessageCounter, timeout=10), 29 | ]) 30 | 31 | loop = asyncio.get_event_loop() 32 | loop.create_task(MessageLoop(bot).run_forever()) 33 | print('Listening ...') 34 | 35 | loop.run_forever() 36 | -------------------------------------------------------------------------------- /examples/chat/guess.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import random 4 | import traceback 5 | import telepot 6 | from telepot.loop import MessageLoop 7 | from telepot.delegate import per_chat_id, create_open, pave_event_space 8 | 9 | """ 10 | $ python3.5 guess.py <token> 11 | 12 | Guess a number: 13 | 14 | 1. Send the bot anything to start a game. 15 | 2. The bot randomly picks an integer between 0-99. 16 | 3. You make a guess. 17 | 4. The bot tells you to go higher or lower. 18 | 5. Repeat step 3 and 4, until guess is correct. 19 | """ 20 | 21 | class Player(telepot.helper.ChatHandler): 22 | def __init__(self, *args, **kwargs): 23 | super(Player, self).__init__(*args, **kwargs) 24 | self._answer = random.randint(0,99) 25 | 26 | def _hint(self, answer, guess): 27 | if answer > guess: 28 | return 'larger' 29 | else: 30 | return 'smaller' 31 | 32 | def open(self, initial_msg, seed): 33 | self.sender.sendMessage('Guess my number') 34 | return True # prevent on_message() from being called on the initial message 35 | 36 | def on_chat_message(self, msg): 37 | content_type, chat_type, chat_id = telepot.glance(msg) 38 | 39 | if content_type != 'text': 40 | self.sender.sendMessage('Give me a number, please.') 41 | return 42 | 43 | try: 44 | guess = int(msg['text']) 45 | except ValueError: 46 | self.sender.sendMessage('Give me a number, please.') 47 | return 48 | 49 | # check the guess against the answer ... 50 | if guess != self._answer: 51 | # give a descriptive hint 52 | hint = self._hint(self._answer, guess) 53 | self.sender.sendMessage(hint) 54 | else: 55 | self.sender.sendMessage('Correct!') 56 | self.close() 57 | 58 | def on__idle(self, event): 59 | self.sender.sendMessage('Game expired. The answer is %d' % self._answer) 60 | self.close() 61 | 62 | 63 | TOKEN = sys.argv[1] 64 | 65 | bot = telepot.DelegatorBot(TOKEN, [ 66 | pave_event_space()( 67 | per_chat_id(), create_open, Player, timeout=10), 68 | ]) 69 | MessageLoop(bot).run_as_thread() 70 | print('Listening ...') 71 | 72 | while 1: 73 | time.sleep(10) 74 | -------------------------------------------------------------------------------- /examples/chat/guessa.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import random 4 | import telepot 5 | from telepot.aio.loop import MessageLoop 6 | from telepot.aio.delegate import per_chat_id, create_open, pave_event_space 7 | 8 | """ 9 | $ python3.5 guessa.py <token> 10 | 11 | Guess a number: 12 | 13 | 1. Send the bot anything to start a game. 14 | 2. The bot randomly picks an integer between 0-99. 15 | 3. You make a guess. 16 | 4. The bot tells you to go higher or lower. 17 | 5. Repeat step 3 and 4, until guess is correct. 18 | """ 19 | 20 | class Player(telepot.aio.helper.ChatHandler): 21 | def __init__(self, *args, **kwargs): 22 | super(Player, self).__init__(*args, **kwargs) 23 | self._answer = random.randint(0,99) 24 | 25 | def _hint(self, answer, guess): 26 | if answer > guess: 27 | return 'larger' 28 | else: 29 | return 'smaller' 30 | 31 | async def open(self, initial_msg, seed): 32 | await self.sender.sendMessage('Guess my number') 33 | return True # prevent on_message() from being called on the initial message 34 | 35 | async def on_chat_message(self, msg): 36 | content_type, chat_type, chat_id = telepot.glance(msg) 37 | 38 | if content_type != 'text': 39 | await self.sender.sendMessage('Give me a number, please.') 40 | return 41 | 42 | try: 43 | guess = int(msg['text']) 44 | except ValueError: 45 | await self.sender.sendMessage('Give me a number, please.') 46 | return 47 | 48 | # check the guess against the answer ... 49 | if guess != self._answer: 50 | # give a descriptive hint 51 | hint = self._hint(self._answer, guess) 52 | await self.sender.sendMessage(hint) 53 | else: 54 | await self.sender.sendMessage('Correct!') 55 | self.close() 56 | 57 | async def on__idle(self, event): 58 | await self.sender.sendMessage('Game expired. The answer is %d' % self._answer) 59 | self.close() 60 | 61 | 62 | TOKEN = sys.argv[1] 63 | 64 | bot = telepot.aio.DelegatorBot(TOKEN, [ 65 | pave_event_space()( 66 | per_chat_id(), create_open, Player, timeout=10), 67 | ]) 68 | 69 | loop = asyncio.get_event_loop() 70 | loop.create_task(MessageLoop(bot).run_forever()) 71 | print('Listening ...') 72 | 73 | loop.run_forever() 74 | -------------------------------------------------------------------------------- /examples/deeplinking/README.md: -------------------------------------------------------------------------------- 1 | # Deep Linking Examples 2 | 3 | ``` 4 | $ python2.7 flask_deeplinking.py <bot_username> <token> <listening_port> https://<domain>/abc 5 | ``` 6 | 7 | 1. Open browser, visit: `https://<domain>/link` 8 | 2. Click on the link 9 | 3. On Telegram conversation, click on the `START` button 10 | 4. Bot should receive a message `/start ghijk`, where `ghijk` is the payload embedded in the link. 11 | You may use this payload to identify the user, then his Telegram `chat_id`. 12 | 13 | ## Deep Linking Explanation 14 | 15 | Telegram's introduction to [deep linking](https://core.telegram.org/bots#deep-linking) 16 | may be a bit confusing. I try to give a clearer explanation here. 17 | 18 | 1. You have a database of users. Each user has an ID. Suppose you want your Telegram bot 19 | to communicate with user `123`, but you don't know his Telegram `chat_id` 20 | (which the bot needs in order to send messages to him). 21 | How do you "entice" him to talk to the bot, thus revealing his `chat_id`? 22 | You put a link on a web page. 23 | 24 | 2. But the link has to be "personalized". You want each user to press on a slightly 25 | different link in order to distinguish them. One way to do that is to embed user ID 26 | in the link. However, user IDs are *not* something you want to expose, so you generate 27 | a (temporary) *key* associated with a user ID, and *embed that key in the link*. 28 | If user `123` has the key `abcde`, his personalized link will be: 29 | 30 | ``` 31 | https://telegram.me/<bot_username>?start=abcde 32 | ``` 33 | 34 | 3. Someone clicks on the link, and is led to a conversation with your bot. 35 | When he presses the `START` button, your bot will receive a message: 36 | 37 | ``` 38 | /start abcde 39 | ``` 40 | 41 | 4. On receiving that message, the bot sees that `abcde` is associated with user `123`. 42 | Telegram `chat_id` can also be extracted from the message. 43 | Knowing user `123`'s `chat_id`, the bot can send him messages afterwards. 44 | 45 | Telegram's introduction refers often to "Memcache", by which they only mean a datastore 46 | that remembers key-user ID associations. In a simple experiment, a dictionary will do. 47 | In real world, you may use Memcached (the memory caching software) or a database table. 48 | -------------------------------------------------------------------------------- /examples/deeplinking/flask_deeplinking.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from flask import Flask, request 3 | import telepot 4 | from telepot.loop import OrderedWebhook 5 | 6 | """ 7 | $ python2.7 flask_deeplinking.py <bot_username> <token> <listening_port> https://<domain>/webhook 8 | 9 | Webhook path is '/webhook'. 10 | Initial webpage is '/link'. 11 | 12 | 1. Open browser, visit: `https://<domain>/link` 13 | 2. Click on the link 14 | 3. On Telegram conversation, click on the `START` button 15 | 4. Bot should receive a message `/start ghijk`, where `ghijk` is the payload embedded in the link. 16 | You may use this payload to identify the user, then his Telegram `chat_id`. 17 | """ 18 | 19 | key_id_map = { 'ghijk' : 123 } 20 | 21 | def handle(msg): 22 | content_type, chat_type, chat_id = telepot.glance(msg) 23 | print 'Chat Message:', content_type, chat_type, chat_id 24 | 25 | if content_type == 'text': 26 | text = msg['text'] 27 | print 'Text:', text 28 | 29 | if text.startswith('/start'): 30 | try: 31 | command, payload = text.split(' ') 32 | 33 | print 'Payload:', payload 34 | print 'User ID:', key_id_map[payload] 35 | print 'chat_id:', chat_id 36 | 37 | except ValueError: 38 | print 'No payload, or more than one chunk of payload' 39 | 40 | except KeyError: 41 | print 'Invalid key, no corresponding User ID' 42 | 43 | 44 | BOT_USERNAME = sys.argv[1] 45 | TOKEN = sys.argv[2] 46 | PORT = int(sys.argv[3]) 47 | URL = sys.argv[4] 48 | 49 | app = Flask(__name__) 50 | bot = telepot.Bot(TOKEN) 51 | webhook = OrderedWebhook(bot, handle) 52 | 53 | @app.route('/link', methods=['GET', 'POST']) 54 | def display_link(): 55 | first_key_in_database = key_id_map.items()[0][0] 56 | return '<a href="https://telegram.me/%s?start=%s">Open conversation with bot</a>' % (BOT_USERNAME, first_key_in_database) 57 | 58 | @app.route('/webhook', methods=['GET', 'POST']) 59 | def pass_update(): 60 | webhook.feed(request.data) 61 | return 'OK' 62 | 63 | if __name__ == '__main__': 64 | try: 65 | bot.setWebhook(URL) 66 | # Sometimes it would raise this error, but webhook still set successfully. 67 | except telepot.exception.TooManyRequestsError: 68 | pass 69 | 70 | webhook.run_as_thread() 71 | app.run(port=PORT, debug=True) 72 | -------------------------------------------------------------------------------- /examples/event/alarm.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.delegate import per_chat_id, create_open, pave_event_space 6 | 7 | """ 8 | $ python3.6 alarm.py <token> 9 | 10 | Send a number which indicates the delay in seconds. The bot will send you an 11 | alarm message after such a delay. It illustrates how to use the built-in 12 | scheduler to schedule custom events for later. 13 | 14 | To design a custom event, you first have to invent a *flavor*. To prevent flavors 15 | from colliding with those of Telegram messages, events are given flavors prefixed 16 | with `_` by convention. Then, follow these steps, which are further detailed by 17 | comments in the code: 18 | 19 | 1. Customize routing table so the correct function gets called on seeing the event 20 | 2. Define event-handling function 21 | 3. Provide the event spec when scheduling events 22 | """ 23 | 24 | class AlarmSetter(telepot.helper.ChatHandler): 25 | def __init__(self, *args, **kwargs): 26 | super(AlarmSetter, self).__init__(*args, **kwargs) 27 | 28 | # 1. Customize the routing table: 29 | # On seeing an event of flavor `_alarm`, call `self.on__alarm`. 30 | # To prevent flavors from colliding with those of Telegram messages, 31 | # events are given flavors prefixed with `_` by convention. Also by 32 | # convention is that the event-handling function is named `on_` 33 | # followed by flavor, leading to the double underscore. 34 | self.router.routing_table['_alarm'] = self.on__alarm 35 | 36 | # 2. Define event-handling function 37 | def on__alarm(self, event): 38 | print(event) # see what the event object actually looks like 39 | self.sender.sendMessage('Beep beep, time to wake up!') 40 | 41 | def on_chat_message(self, msg): 42 | try: 43 | delay = float(msg['text']) 44 | 45 | # 3. Schedule event 46 | # The second argument is the event spec: a 2-tuple of (flavor, dict). 47 | # Put any custom data in the dict. Retrieve them in the event-handling function. 48 | self.scheduler.event_later(delay, ('_alarm', {'payload': delay})) 49 | self.sender.sendMessage('Got it. Alarm is set at %.1f seconds from now.' % delay) 50 | except ValueError: 51 | self.sender.sendMessage('Not a number. No alarm set.') 52 | 53 | 54 | TOKEN = sys.argv[1] 55 | 56 | bot = telepot.DelegatorBot(TOKEN, [ 57 | pave_event_space()( 58 | per_chat_id(), create_open, AlarmSetter, timeout=10), 59 | ]) 60 | MessageLoop(bot).run_as_thread() 61 | print('Listening ...') 62 | 63 | while 1: 64 | time.sleep(10) 65 | -------------------------------------------------------------------------------- /examples/event/alarma.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import telepot 4 | from telepot.aio.loop import MessageLoop 5 | from telepot.aio.delegate import per_chat_id, create_open, pave_event_space 6 | 7 | """ 8 | $ python3.6 alarma.py <token> 9 | 10 | Send a number which indicates the delay in seconds. The bot will send you an 11 | alarm message after such a delay. It illustrates how to use the built-in 12 | scheduler to schedule custom events for later. 13 | 14 | To design a custom event, you first have to invent a *flavor*. To prevent flavors 15 | from colliding with those of Telegram messages, events are given flavors prefixed 16 | with `_` by convention. Then, follow these steps, which are further detailed by 17 | comments in the code: 18 | 19 | 1. Customize routing table so the correct function gets called on seeing the event 20 | 2. Define event-handling function 21 | 3. Provide the event spec when scheduling events 22 | """ 23 | 24 | class AlarmSetter(telepot.aio.helper.ChatHandler): 25 | def __init__(self, *args, **kwargs): 26 | super(AlarmSetter, self).__init__(*args, **kwargs) 27 | 28 | # 1. Customize the routing table: 29 | # On seeing an event of flavor `_alarm`, call `self.on__alarm`. 30 | # To prevent flavors from colliding with those of Telegram messages, 31 | # events are given flavors prefixed with `_` by convention. Also by 32 | # convention is that the event-handling function is named `on_` 33 | # followed by flavor, leading to the double underscore. 34 | self.router.routing_table['_alarm'] = self.on__alarm 35 | 36 | # 2. Define event-handling function 37 | async def on__alarm(self, event): 38 | print(event) # see what the event object actually looks like 39 | await self.sender.sendMessage('Beep beep, time to wake up!') 40 | 41 | async def on_chat_message(self, msg): 42 | try: 43 | delay = float(msg['text']) 44 | 45 | # 3. Schedule event 46 | # The second argument is the event spec: a 2-tuple of (flavor, dict). 47 | # Put any custom data in the dict. Retrieve them in the event-handling function. 48 | self.scheduler.event_later(delay, ('_alarm', {'payload': delay})) 49 | await self.sender.sendMessage('Got it. Alarm is set at %.1f seconds from now.' % delay) 50 | except ValueError: 51 | await self.sender.sendMessage('Not a number. No alarm set.') 52 | 53 | 54 | TOKEN = sys.argv[1] 55 | 56 | bot = telepot.aio.DelegatorBot(TOKEN, [ 57 | pave_event_space()( 58 | per_chat_id(), create_open, AlarmSetter, timeout=10), 59 | ]) 60 | 61 | loop = asyncio.get_event_loop() 62 | loop.create_task(MessageLoop(bot).run_forever()) 63 | print('Listening ...') 64 | 65 | loop.run_forever() 66 | -------------------------------------------------------------------------------- /examples/inline/README.md: -------------------------------------------------------------------------------- 1 | # Inline Query Examples 2 | 3 | ``` 4 | $ python3.5 inline.py <token> # traditional 5 | $ python3.5 inlinea.py <token> # async 6 | ``` 7 | 8 | It demonstrates answering inline query and getting chosen inline results. 9 | 10 | **[Traditional »](inline.py)** 11 | **[Async »](inlinea.py)** 12 | -------------------------------------------------------------------------------- /examples/inline/inline.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | from telepot.delegate import per_inline_from_id, create_open, pave_event_space 6 | 7 | """ 8 | $ python3.5 inline.py <token> 9 | 10 | It demonstrates answering inline query and getting chosen inline results. 11 | """ 12 | 13 | class InlineHandler(telepot.helper.InlineUserHandler, telepot.helper.AnswererMixin): 14 | def __init__(self, *args, **kwargs): 15 | super(InlineHandler, self).__init__(*args, **kwargs) 16 | 17 | def on_inline_query(self, msg): 18 | def compute_answer(): 19 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 20 | print(self.id, ':', 'Inline Query:', query_id, from_id, query_string) 21 | 22 | articles = [{'type': 'article', 23 | 'id': 'abc', 'title': query_string, 'message_text': query_string}] 24 | 25 | return articles 26 | 27 | self.answerer.answer(msg, compute_answer) 28 | 29 | def on_chosen_inline_result(self, msg): 30 | from pprint import pprint 31 | pprint(msg) 32 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 33 | print(self.id, ':', 'Chosen Inline Result:', result_id, from_id, query_string) 34 | 35 | 36 | TOKEN = sys.argv[1] 37 | 38 | bot = telepot.DelegatorBot(TOKEN, [ 39 | pave_event_space()( 40 | per_inline_from_id(), create_open, InlineHandler, timeout=10), 41 | ]) 42 | MessageLoop(bot).run_as_thread() 43 | print('Listening ...') 44 | 45 | while 1: 46 | time.sleep(10) 47 | -------------------------------------------------------------------------------- /examples/inline/inlinea.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import telepot 4 | from telepot.aio.loop import MessageLoop 5 | from telepot.aio.helper import InlineUserHandler, AnswererMixin 6 | from telepot.aio.delegate import per_inline_from_id, create_open, pave_event_space 7 | 8 | """ 9 | $ python3.5 inlinea.py <token> 10 | 11 | It demonstrates answering inline query and getting chosen inline results. 12 | """ 13 | 14 | class InlineHandler(InlineUserHandler, AnswererMixin): 15 | def __init__(self, *args, **kwargs): 16 | super(InlineHandler, self).__init__(*args, **kwargs) 17 | 18 | def on_inline_query(self, msg): 19 | def compute_answer(): 20 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 21 | print(self.id, ':', 'Inline Query:', query_id, from_id, query_string) 22 | 23 | articles = [{'type': 'article', 24 | 'id': 'abc', 'title': query_string, 'message_text': query_string}] 25 | 26 | return articles 27 | 28 | self.answerer.answer(msg, compute_answer) 29 | 30 | def on_chosen_inline_result(self, msg): 31 | from pprint import pprint 32 | pprint(msg) 33 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 34 | print(self.id, ':', 'Chosen Inline Result:', result_id, from_id, query_string) 35 | 36 | 37 | TOKEN = sys.argv[1] 38 | 39 | bot = telepot.aio.DelegatorBot(TOKEN, [ 40 | pave_event_space()( 41 | per_inline_from_id(), create_open, InlineHandler, timeout=10), 42 | ]) 43 | loop = asyncio.get_event_loop() 44 | 45 | loop.create_task(MessageLoop(bot).run_forever()) 46 | print('Listening ...') 47 | 48 | loop.run_forever() 49 | -------------------------------------------------------------------------------- /examples/payment/payment.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from pprint import pprint 4 | import telepot 5 | from telepot.loop import MessageLoop 6 | from telepot.namedtuple import LabeledPrice, ShippingOption 7 | from telepot.delegate import ( 8 | per_invoice_payload, pave_event_space, create_open, 9 | per_message, call) 10 | 11 | """ 12 | Run it by: 13 | $ python3.5 script.py <bot-token> <payment-provider-token> 14 | """ 15 | 16 | class OrderProcessor(telepot.helper.InvoiceHandler): 17 | def __init__(self, *args, **kwargs): 18 | super(OrderProcessor, self).__init__(*args, **kwargs) 19 | 20 | def on_shipping_query(self, msg): 21 | query_id, from_id, invoice_payload = telepot.glance(msg, flavor='shipping_query') 22 | 23 | print('Shipping query:') 24 | pprint(msg) 25 | 26 | bot.answerShippingQuery( 27 | query_id, True, 28 | shipping_options=[ 29 | ShippingOption(id='fedex', title='FedEx', prices=[ 30 | LabeledPrice(label='Local', amount=345), 31 | LabeledPrice(label='International', amount=2345)]), 32 | ShippingOption(id='dhl', title='DHL', prices=[ 33 | LabeledPrice(label='Local', amount=342), 34 | LabeledPrice(label='International', amount=1234)])]) 35 | 36 | def on_pre_checkout_query(self, msg): 37 | query_id, from_id, invoice_payload = telepot.glance(msg, flavor='pre_checkout_query') 38 | 39 | print('Pre-Checkout query:') 40 | pprint(msg) 41 | 42 | bot.answerPreCheckoutQuery(query_id, True) 43 | 44 | def on_chat_message(self, msg): 45 | content_type, chat_type, chat_id = telepot.glance(msg) 46 | 47 | if content_type == 'successful_payment': 48 | print('Successful payment RECEIVED!!!') 49 | pprint(msg) 50 | else: 51 | print('Chat message:') 52 | pprint(msg) 53 | 54 | def send_invoice(seed_tuple): 55 | msg = seed_tuple[1] 56 | 57 | content_type, chat_type, chat_id = telepot.glance(msg) 58 | 59 | if content_type == 'text': 60 | sent = bot.sendInvoice( 61 | chat_id, "Nick's Hand Cream", "Keep a man's hand like a woman's", 62 | payload='a-string-identifying-related-payment-messages-tuvwxyz', 63 | provider_token=PAYMENT_PROVIDER_TOKEN, 64 | start_parameter='abc', 65 | currency='HKD', prices=[ 66 | LabeledPrice(label='One Case', amount=987), 67 | LabeledPrice(label='Package', amount=12)], 68 | need_shipping_address=True, is_flexible=True) # required for shipping query 69 | 70 | print('Invoice sent:') 71 | pprint(sent) 72 | 73 | 74 | TOKEN = sys.argv[1] 75 | PAYMENT_PROVIDER_TOKEN = sys.argv[2] 76 | 77 | bot = telepot.DelegatorBot(TOKEN, [ 78 | (per_message(flavors=['chat']), call(send_invoice)), 79 | pave_event_space()( 80 | per_invoice_payload(), create_open, OrderProcessor, timeout=30, 81 | ) 82 | ]) 83 | 84 | MessageLoop(bot).run_as_thread() 85 | 86 | while 1: 87 | time.sleep(10) 88 | -------------------------------------------------------------------------------- /examples/payment/paymenta.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from pprint import pprint 4 | import telepot 5 | import telepot.aio 6 | from telepot.aio.loop import MessageLoop 7 | from telepot.namedtuple import LabeledPrice, ShippingOption 8 | from telepot.aio.delegate import ( 9 | per_invoice_payload, pave_event_space, create_open, 10 | per_message, call) 11 | 12 | """ 13 | Run it by: 14 | $ python3.5 script.py <bot-token> <payment-provider-token> 15 | """ 16 | 17 | class OrderProcessor(telepot.aio.helper.InvoiceHandler): 18 | def __init__(self, *args, **kwargs): 19 | super(OrderProcessor, self).__init__(*args, **kwargs) 20 | 21 | async def on_shipping_query(self, msg): 22 | query_id, from_id, invoice_payload = telepot.glance(msg, flavor='shipping_query') 23 | 24 | print('Shipping query:') 25 | pprint(msg) 26 | 27 | await bot.answerShippingQuery( 28 | query_id, True, 29 | shipping_options=[ 30 | ShippingOption(id='fedex', title='FedEx', prices=[ 31 | LabeledPrice(label='Local', amount=345), 32 | LabeledPrice(label='International', amount=2345)]), 33 | ShippingOption(id='dhl', title='DHL', prices=[ 34 | LabeledPrice(label='Local', amount=342), 35 | LabeledPrice(label='International', amount=1234)])]) 36 | 37 | async def on_pre_checkout_query(self, msg): 38 | query_id, from_id, invoice_payload = telepot.glance(msg, flavor='pre_checkout_query') 39 | 40 | print('Pre-Checkout query:') 41 | pprint(msg) 42 | 43 | await bot.answerPreCheckoutQuery(query_id, True) 44 | 45 | def on_chat_message(self, msg): 46 | content_type, chat_type, chat_id = telepot.glance(msg) 47 | 48 | if content_type == 'successful_payment': 49 | print('Successful payment RECEIVED!!!') 50 | pprint(msg) 51 | else: 52 | print('Chat message:') 53 | pprint(msg) 54 | 55 | async def send_invoice(seed_tuple): 56 | msg = seed_tuple[1] 57 | 58 | content_type, chat_type, chat_id = telepot.glance(msg) 59 | 60 | if content_type == 'text': 61 | sent = await bot.sendInvoice( 62 | chat_id, "Nick's Hand Cream", "Keep a man's hand like a woman's", 63 | payload='a-string-identifying-related-payment-messages-tuvwxyz', 64 | provider_token=PAYMENT_PROVIDER_TOKEN, 65 | start_parameter='abc', 66 | currency='HKD', prices=[ 67 | LabeledPrice(label='One Case', amount=987), 68 | LabeledPrice(label='Package', amount=12)], 69 | need_shipping_address=True, is_flexible=True) # required for shipping query 70 | 71 | print('Invoice sent:') 72 | pprint(sent) 73 | 74 | 75 | TOKEN = sys.argv[1] 76 | PAYMENT_PROVIDER_TOKEN = sys.argv[2] 77 | 78 | bot = telepot.aio.DelegatorBot(TOKEN, [ 79 | (per_message(flavors=['chat']), call(send_invoice)), 80 | pave_event_space()( 81 | per_invoice_payload(), create_open, OrderProcessor, timeout=30, 82 | ) 83 | ]) 84 | 85 | loop = asyncio.get_event_loop() 86 | loop.create_task(MessageLoop(bot).run_forever()) 87 | 88 | loop.run_forever() 89 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple Examples 2 | 3 | ### [Dicey Clock](diceyclock.py) 4 | 5 | After **inserting token** in the source code, run it: 6 | 7 | ``` 8 | $ python2.7 diceyclock.py 9 | ``` 10 | 11 | [Here is a tutorial](http://www.instructables.com/id/Set-up-Telegram-Bot-on-Raspberry-Pi/) 12 | teaching you how to setup a bot on Raspberry Pi. This simple bot does nothing 13 | but accepts two commands: 14 | 15 | - `/roll` - reply with a random integer between 1 and 6, like rolling a dice. 16 | - `/time` - reply with the current time, like a clock. 17 | 18 | ### Emodi - an Emoji Unicode Decoder 19 | 20 | You send it some emoji, it tells you the unicodes. 21 | 22 | **[Traditional version »](emodi.py)** 23 | 24 | But if you really want to put emoji in a string, I recommend using this 25 | **[emoji](https://pypi.python.org/pypi/emoji/)** package. 26 | 27 | ### Simple Skeleton 28 | 29 | ``` 30 | $ python2.7 skeleton.py <token> # traditional 31 | $ python3.5 skeletona.py <token> # async 32 | ``` 33 | 34 | **[Traditional »](skeleton.py)** 35 | **[Async »](skeletona.py)** 36 | 37 | ### Various keyboards and buttons 38 | 39 | ``` 40 | $ python3.5 skeleton_route.py <token> # traditional 41 | $ python3.5 skeletona_route.py <token> # async 42 | ``` 43 | 44 | It demonstrates: 45 | 46 | - passing a routing table to `MessageLoop` to filter flavors. 47 | - the use of custom keyboard and inline keyboard, and their various buttons. 48 | 49 | Remember to `/setinline` and `/setinlinefeedback` to enable inline mode for your bot. 50 | 51 | It works like this: 52 | 53 | - First, you send it one of these 4 characters - `c`, `i`, `h`, `f` - and it replies accordingly: 54 | - `c` - a custom keyboard with various buttons 55 | - `i` - an inline keyboard with various buttons 56 | - `h` - hide custom keyboard 57 | - `f` - force reply 58 | - Press various buttons to see their effects 59 | - Within inline mode, what you get back depends on the **last character** of the query: 60 | - `a` - a list of articles 61 | - `p` - a list of photos 62 | - `b` - to see a button above the inline results to switch back to a private chat with the bot 63 | 64 | **[Traditional »](skeleton_route.py)** 65 | **[Async »](skeletona_route.py)** 66 | -------------------------------------------------------------------------------- /examples/simple/diceyclock.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | import datetime 4 | import telepot 5 | from telepot.loop import MessageLoop 6 | 7 | """ 8 | After **inserting token** in the source code, run it: 9 | 10 | ``` 11 | $ python2.7 diceyclock.py 12 | ``` 13 | 14 | [Here is a tutorial](http://www.instructables.com/id/Set-up-Telegram-Bot-on-Raspberry-Pi/) 15 | teaching you how to setup a bot on Raspberry Pi. This simple bot does nothing 16 | but accepts two commands: 17 | 18 | - `/roll` - reply with a random integer between 1 and 6, like rolling a dice. 19 | - `/time` - reply with the current time, like a clock. 20 | """ 21 | 22 | def handle(msg): 23 | chat_id = msg['chat']['id'] 24 | command = msg['text'] 25 | 26 | print 'Got command: %s' % command 27 | 28 | if command == '/roll': 29 | bot.sendMessage(chat_id, random.randint(1,6)) 30 | elif command == '/time': 31 | bot.sendMessage(chat_id, str(datetime.datetime.now())) 32 | 33 | bot = telepot.Bot('*** INSERT TOKEN ***') 34 | 35 | MessageLoop(bot, handle).run_as_thread() 36 | print 'I am listening ...' 37 | 38 | while 1: 39 | time.sleep(10) 40 | -------------------------------------------------------------------------------- /examples/simple/emodi.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | import telepot.namedtuple 5 | from telepot.loop import MessageLoop 6 | 7 | """ 8 | $ python2.7 emodi.py <token> 9 | 10 | Emodi: An Emoji Unicode Decoder - You send it some emoji, it tells you the unicodes. 11 | 12 | Caution: Python's treatment of unicode characters longer than 2 bytes (which 13 | most emojis are) varies across versions and platforms. I have tested this program 14 | on Python2.7.9/Raspbian. If you try it on other versions/platforms, the length- 15 | checking and substring-extraction below may not work as expected. 16 | """ 17 | 18 | def handle(msg): 19 | content_type, chat_type, chat_id = telepot.glance(msg) 20 | m = telepot.namedtuple.Message(**msg) 21 | 22 | if chat_id < 0: 23 | # group message 24 | print 'Received a %s from %s, by %s' % (content_type, m.chat, m.from_) 25 | else: 26 | # private message 27 | print 'Received a %s from %s' % (content_type, m.chat) # m.chat == m.from_ 28 | 29 | if content_type == 'text': 30 | reply = '' 31 | 32 | # For long messages, only return the first 10 characters. 33 | if len(msg['text']) > 10: 34 | reply = u'First 10 characters:\n' 35 | 36 | # Length-checking and substring-extraction may work differently 37 | # depending on Python versions and platforms. See above. 38 | 39 | reply += msg['text'][:10].encode('unicode-escape').decode('ascii') 40 | bot.sendMessage(chat_id, reply) 41 | 42 | 43 | TOKEN = sys.argv[1] # get token from command-line 44 | 45 | bot = telepot.Bot(TOKEN) 46 | MessageLoop(bot, handle).run_as_thread() 47 | print 'Listening ...' 48 | 49 | # Keep the program running. 50 | while 1: 51 | time.sleep(10) 52 | -------------------------------------------------------------------------------- /examples/simple/skeleton.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.loop import MessageLoop 5 | 6 | """ 7 | $ python2.7 skeleton.py <token> 8 | 9 | A skeleton for your telepot programs. 10 | """ 11 | 12 | def handle(msg): 13 | flavor = telepot.flavor(msg) 14 | 15 | summary = telepot.glance(msg, flavor=flavor) 16 | print flavor, summary 17 | 18 | 19 | TOKEN = sys.argv[1] # get token from command-line 20 | 21 | bot = telepot.Bot(TOKEN) 22 | MessageLoop(bot, handle).run_as_thread() 23 | print 'Listening ...' 24 | 25 | # Keep the program running. 26 | while 1: 27 | time.sleep(10) 28 | -------------------------------------------------------------------------------- /examples/simple/skeleton_route.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import threading 4 | import random 5 | import telepot 6 | from telepot.loop import MessageLoop 7 | from telepot.namedtuple import ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove, ForceReply 8 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 9 | from telepot.namedtuple import InlineQueryResultArticle, InlineQueryResultPhoto, InputTextMessageContent 10 | 11 | """ 12 | $ python3.5 skeleton_route.py <token> 13 | 14 | It demonstrates: 15 | - passing a routing table to `MessageLoop` to filter flavors. 16 | - the use of custom keyboard and inline keyboard, and their various buttons. 17 | 18 | Remember to `/setinline` and `/setinlinefeedback` to enable inline mode for your bot. 19 | 20 | It works like this: 21 | 22 | - First, you send it one of these 4 characters - `c`, `i`, `h`, `f` - and it replies accordingly: 23 | - `c` - a custom keyboard with various buttons 24 | - `i` - an inline keyboard with various buttons 25 | - `h` - hide custom keyboard 26 | - `f` - force reply 27 | - Press various buttons to see their effects 28 | - Within inline mode, what you get back depends on the **last character** of the query: 29 | - `a` - a list of articles 30 | - `p` - a list of photos 31 | - `b` - to see a button above the inline results to switch back to a private chat with the bot 32 | """ 33 | 34 | message_with_inline_keyboard = None 35 | 36 | def on_chat_message(msg): 37 | content_type, chat_type, chat_id = telepot.glance(msg) 38 | print('Chat:', content_type, chat_type, chat_id) 39 | 40 | if content_type != 'text': 41 | return 42 | 43 | command = msg['text'][-1:].lower() 44 | 45 | if command == 'c': 46 | markup = ReplyKeyboardMarkup(keyboard=[ 47 | ['Plain text', KeyboardButton(text='Text only')], 48 | [dict(text='Phone', request_contact=True), KeyboardButton(text='Location', request_location=True)], 49 | ]) 50 | bot.sendMessage(chat_id, 'Custom keyboard with various buttons', reply_markup=markup) 51 | elif command == 'i': 52 | markup = InlineKeyboardMarkup(inline_keyboard=[ 53 | [dict(text='Telegram URL', url='https://core.telegram.org/')], 54 | [InlineKeyboardButton(text='Callback - show notification', callback_data='notification')], 55 | [dict(text='Callback - show alert', callback_data='alert')], 56 | [InlineKeyboardButton(text='Callback - edit message', callback_data='edit')], 57 | [dict(text='Switch to using bot inline', switch_inline_query='initial query')], 58 | ]) 59 | 60 | global message_with_inline_keyboard 61 | message_with_inline_keyboard = bot.sendMessage(chat_id, 'Inline keyboard with various buttons', reply_markup=markup) 62 | elif command == 'h': 63 | markup = ReplyKeyboardRemove() 64 | bot.sendMessage(chat_id, 'Hide custom keyboard', reply_markup=markup) 65 | elif command == 'f': 66 | markup = ForceReply() 67 | bot.sendMessage(chat_id, 'Force reply', reply_markup=markup) 68 | 69 | 70 | def on_callback_query(msg): 71 | query_id, from_id, data = telepot.glance(msg, flavor='callback_query') 72 | print('Callback query:', query_id, from_id, data) 73 | 74 | if data == 'notification': 75 | bot.answerCallbackQuery(query_id, text='Notification at top of screen') 76 | elif data == 'alert': 77 | bot.answerCallbackQuery(query_id, text='Alert!', show_alert=True) 78 | elif data == 'edit': 79 | global message_with_inline_keyboard 80 | 81 | if message_with_inline_keyboard: 82 | msg_idf = telepot.message_identifier(message_with_inline_keyboard) 83 | bot.editMessageText(msg_idf, 'NEW MESSAGE HERE!!!!!') 84 | else: 85 | bot.answerCallbackQuery(query_id, text='No previous message to edit') 86 | 87 | 88 | def on_inline_query(msg): 89 | def compute(): 90 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 91 | print('%s: Computing for: %s' % (threading.current_thread().name, query_string)) 92 | 93 | articles = [InlineQueryResultArticle( 94 | id='abcde', title='Telegram', input_message_content=InputTextMessageContent(message_text='Telegram is a messaging app')), 95 | dict(type='article', 96 | id='fghij', title='Google', input_message_content=dict(message_text='Google is a search engine'))] 97 | 98 | photo1_url = 'https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf' 99 | photo2_url = 'https://www.telegram.org/img/t_logo.png' 100 | photos = [InlineQueryResultPhoto( 101 | id='12345', photo_url=photo1_url, thumb_url=photo1_url), 102 | dict(type='photo', 103 | id='67890', photo_url=photo2_url, thumb_url=photo2_url)] 104 | 105 | result_type = query_string[-1:].lower() 106 | 107 | if result_type == 'a': 108 | return articles 109 | elif result_type == 'p': 110 | return photos 111 | else: 112 | results = articles if random.randint(0,1) else photos 113 | if result_type == 'b': 114 | return dict(results=results, switch_pm_text='Back to Bot', switch_pm_parameter='Optional_start_parameter') 115 | else: 116 | return dict(results=results) 117 | 118 | answerer.answer(msg, compute) 119 | 120 | 121 | def on_chosen_inline_result(msg): 122 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 123 | print('Chosen Inline Result:', result_id, from_id, query_string) 124 | 125 | 126 | TOKEN = sys.argv[1] # get token from command-line 127 | 128 | bot = telepot.Bot(TOKEN) 129 | answerer = telepot.helper.Answerer(bot) 130 | 131 | MessageLoop(bot, {'chat': on_chat_message, 132 | 'callback_query': on_callback_query, 133 | 'inline_query': on_inline_query, 134 | 'chosen_inline_result': on_chosen_inline_result}).run_as_thread() 135 | print('Listening ...') 136 | 137 | # Keep the program running. 138 | while 1: 139 | time.sleep(10) 140 | -------------------------------------------------------------------------------- /examples/simple/skeletona.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import telepot 4 | import telepot.aio 5 | from telepot.aio.loop import MessageLoop 6 | 7 | """ 8 | $ python3.5 skeletona.py <token> 9 | 10 | A skeleton for your async telepot programs. 11 | """ 12 | 13 | def handle(msg): 14 | flavor = telepot.flavor(msg) 15 | 16 | summary = telepot.glance(msg, flavor=flavor) 17 | print(flavor, summary) 18 | 19 | 20 | TOKEN = sys.argv[1] # get token from command-line 21 | 22 | bot = telepot.aio.Bot(TOKEN) 23 | loop = asyncio.get_event_loop() 24 | 25 | loop.create_task(MessageLoop(bot, handle).run_forever()) 26 | print('Listening ...') 27 | 28 | loop.run_forever() 29 | -------------------------------------------------------------------------------- /examples/simple/skeletona_route.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import random 4 | import telepot 5 | import telepot.aio 6 | from telepot.aio.loop import MessageLoop 7 | from telepot.namedtuple import ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove, ForceReply 8 | from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton 9 | from telepot.namedtuple import InlineQueryResultArticle, InlineQueryResultPhoto, InputTextMessageContent 10 | 11 | """ 12 | $ python3.5 skeletona_route.py <token> 13 | 14 | It demonstrates: 15 | - passing a routing table to `MessageLoop` to filter flavors. 16 | - the use of custom keyboard and inline keyboard, and their various buttons. 17 | 18 | Remember to `/setinline` and `/setinlinefeedback` to enable inline mode for your bot. 19 | 20 | It works like this: 21 | 22 | - First, you send it one of these 4 characters - `c`, `i`, `h`, `f` - and it replies accordingly: 23 | - `c` - a custom keyboard with various buttons 24 | - `i` - an inline keyboard with various buttons 25 | - `h` - hide custom keyboard 26 | - `f` - force reply 27 | - Press various buttons to see their effects 28 | - Within inline mode, what you get back depends on the **last character** of the query: 29 | - `a` - a list of articles 30 | - `p` - a list of photos 31 | - `b` - to see a button above the inline results to switch back to a private chat with the bot 32 | """ 33 | 34 | message_with_inline_keyboard = None 35 | 36 | async def on_chat_message(msg): 37 | content_type, chat_type, chat_id = telepot.glance(msg) 38 | print('Chat:', content_type, chat_type, chat_id) 39 | 40 | if content_type != 'text': 41 | return 42 | 43 | command = msg['text'][-1:].lower() 44 | 45 | if command == 'c': 46 | markup = ReplyKeyboardMarkup(keyboard=[ 47 | ['Plain text', KeyboardButton(text='Text only')], 48 | [dict(text='Phone', request_contact=True), KeyboardButton(text='Location', request_location=True)], 49 | ]) 50 | await bot.sendMessage(chat_id, 'Custom keyboard with various buttons', reply_markup=markup) 51 | elif command == 'i': 52 | markup = InlineKeyboardMarkup(inline_keyboard=[ 53 | [dict(text='Telegram URL', url='https://core.telegram.org/')], 54 | [InlineKeyboardButton(text='Callback - show notification', callback_data='notification')], 55 | [dict(text='Callback - show alert', callback_data='alert')], 56 | [InlineKeyboardButton(text='Callback - edit message', callback_data='edit')], 57 | [dict(text='Switch to using bot inline', switch_inline_query='initial query')], 58 | ]) 59 | 60 | global message_with_inline_keyboard 61 | message_with_inline_keyboard = await bot.sendMessage(chat_id, 'Inline keyboard with various buttons', reply_markup=markup) 62 | elif command == 'h': 63 | markup = ReplyKeyboardRemove() 64 | await bot.sendMessage(chat_id, 'Hide custom keyboard', reply_markup=markup) 65 | elif command == 'f': 66 | markup = ForceReply() 67 | await bot.sendMessage(chat_id, 'Force reply', reply_markup=markup) 68 | 69 | 70 | async def on_callback_query(msg): 71 | query_id, from_id, data = telepot.glance(msg, flavor='callback_query') 72 | print('Callback query:', query_id, from_id, data) 73 | 74 | if data == 'notification': 75 | await bot.answerCallbackQuery(query_id, text='Notification at top of screen') 76 | elif data == 'alert': 77 | await bot.answerCallbackQuery(query_id, text='Alert!', show_alert=True) 78 | elif data == 'edit': 79 | global message_with_inline_keyboard 80 | 81 | if message_with_inline_keyboard: 82 | msg_idf = telepot.message_identifier(message_with_inline_keyboard) 83 | await bot.editMessageText(msg_idf, 'NEW MESSAGE HERE!!!!!') 84 | else: 85 | await bot.answerCallbackQuery(query_id, text='No previous message to edit') 86 | 87 | 88 | def on_inline_query(msg): 89 | def compute(): 90 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 91 | print('Computing for: %s' % query_string) 92 | 93 | articles = [InlineQueryResultArticle( 94 | id='abcde', title='Telegram', input_message_content=InputTextMessageContent(message_text='Telegram is a messaging app')), 95 | dict(type='article', 96 | id='fghij', title='Google', input_message_content=dict(message_text='Google is a search engine'))] 97 | 98 | photo1_url = 'https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf' 99 | photo2_url = 'https://www.telegram.org/img/t_logo.png' 100 | photos = [InlineQueryResultPhoto( 101 | id='12345', photo_url=photo1_url, thumb_url=photo1_url), 102 | dict(type='photo', 103 | id='67890', photo_url=photo2_url, thumb_url=photo2_url)] 104 | 105 | result_type = query_string[-1:].lower() 106 | 107 | if result_type == 'a': 108 | return articles 109 | elif result_type == 'p': 110 | return photos 111 | else: 112 | results = articles if random.randint(0,1) else photos 113 | if result_type == 'b': 114 | return dict(results=results, switch_pm_text='Back to Bot', switch_pm_parameter='Optional_start_parameter') 115 | else: 116 | return dict(results=results) 117 | 118 | answerer.answer(msg, compute) 119 | 120 | 121 | def on_chosen_inline_result(msg): 122 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 123 | print('Chosen Inline Result:', result_id, from_id, query_string) 124 | 125 | 126 | TOKEN = sys.argv[1] # get token from command-line 127 | 128 | bot = telepot.aio.Bot(TOKEN) 129 | answerer = telepot.aio.helper.Answerer(bot) 130 | 131 | loop = asyncio.get_event_loop() 132 | loop.create_task(MessageLoop(bot, {'chat': on_chat_message, 133 | 'callback_query': on_callback_query, 134 | 'inline_query': on_inline_query, 135 | 'chosen_inline_result': on_chosen_inline_result}).run_forever()) 136 | print('Listening ...') 137 | 138 | loop.run_forever() 139 | -------------------------------------------------------------------------------- /examples/webhook/README.md: -------------------------------------------------------------------------------- 1 | # Webhook Examples 2 | 3 | Traditional version using **[Flask](http://flask.pocoo.org/)** as web server: 4 | 5 | ``` 6 | $ python2.7 flask_skeleton.py <token> <listening_port> https://<domain>/abc 7 | $ python3.5 flask_counter.py <token> <listening_port> https://<domain>/abc 8 | ``` 9 | 10 | Async version using **[aiohttp](http://aiohttp.readthedocs.org/en/stable/)** as web server: 11 | 12 | ``` 13 | $ python3.5 aiohttp_skeletona.py <token> <listening_port> https://<domain>/abc 14 | $ python3.5 aiohttp_countera.py <token> <listening_port> https://<domain>/abc 15 | ``` 16 | 17 | Remember you will have to set up the **webhook URL, SSL certificate, and web server** on your own. 18 | 19 | ## Telepot's Webhook Interface 20 | 21 | Setting up a **[webhook](https://core.telegram.org/bots/api#setwebhook)** is 22 | more complicated than using `getUpdates()` because: 23 | 24 | 1. You have to obtain an URL 25 | 2. You have to obtain and set up an SSL certificate for the URL 26 | 3. You have to set up a web server to handle the POST requests coming from Telegram servers 27 | 28 | Webhook also presents a subtle problem: closely bunched updates may arrive out of order. 29 | That is, update_id `1000` may arrive ahead of update_id `999`, if the two are issued by 30 | Telegram servers very closely. Unless a bot absolutely doesn't care about update order, 31 | it will have to re-order them in some way. 32 | 33 | Telepot has a mechanism to interface with web applications, and it takes care of re-ordering 34 | for you. It is called `OrderedWebhook`. 35 | 36 | ```python 37 | from telepot.loop import OrderedWebhook 38 | 39 | def handle(msg): 40 | # ...... 41 | 42 | bot = telepot.Bot(TOKEN) 43 | webhook = OrderedWebhook(bot, handle) 44 | 45 | webhook.run_as_thread() 46 | ``` 47 | 48 | The web application, upon receiving a POST request, feeds data into the webhook 49 | object. It will re-order the updates if necessary. 50 | Using [Flask](http://flask.pocoo.org/) as the web application framework: 51 | 52 | ```python 53 | from flask import Flask, request 54 | 55 | app = Flask(__name__) 56 | 57 | @app.route('/webhook_path', methods=['GET', 'POST']) 58 | def pass_update(): 59 | webhook.feed(request.data) 60 | return 'OK' 61 | ``` 62 | -------------------------------------------------------------------------------- /examples/webhook/aiohttp_countera.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from aiohttp import web 4 | import telepot 5 | from telepot.aio.loop import OrderedWebhook 6 | from telepot.aio.delegate import per_chat_id, create_open, pave_event_space 7 | 8 | """ 9 | $ python3.5 aiohttp_countera.py <token> <listening_port> <webhook_url> 10 | 11 | Webhook path is '/webhook', therefore: 12 | 13 | <webhook_url>: https://<base>/webhook 14 | """ 15 | 16 | class MessageCounter(telepot.aio.helper.ChatHandler): 17 | def __init__(self, *args, **kwargs): 18 | super(MessageCounter, self).__init__(*args, **kwargs) 19 | self._count = 0 20 | 21 | async def on_chat_message(self, msg): 22 | self._count += 1 23 | await self.sender.sendMessage(self._count) 24 | 25 | async def feeder(request): 26 | data = await request.text() 27 | webhook.feed(data) 28 | return web.Response(body='OK'.encode('utf-8')) 29 | 30 | async def init(app, bot): 31 | app.router.add_route('GET', '/webhook', feeder) 32 | app.router.add_route('POST', '/webhook', feeder) 33 | 34 | await bot.setWebhook(URL) 35 | 36 | 37 | TOKEN = sys.argv[1] 38 | PORT = int(sys.argv[2]) 39 | URL = sys.argv[3] 40 | 41 | loop = asyncio.get_event_loop() 42 | 43 | app = web.Application(loop=loop) 44 | bot = telepot.aio.DelegatorBot(TOKEN, [ 45 | pave_event_space()( 46 | per_chat_id(), create_open, MessageCounter, timeout=10)], 47 | loop=loop) 48 | webhook = OrderedWebhook(bot) 49 | 50 | loop.run_until_complete(init(app, bot)) 51 | 52 | loop.create_task(webhook.run_forever()) 53 | 54 | try: 55 | web.run_app(app, port=PORT) 56 | except KeyboardInterrupt: 57 | pass 58 | -------------------------------------------------------------------------------- /examples/webhook/aiohttp_skeletona.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from aiohttp import web 4 | import telepot 5 | import telepot.aio 6 | from telepot.aio.loop import OrderedWebhook 7 | 8 | """ 9 | $ python3.5 aiohttp_skeletona.py <token> <listening_port> <webhook_url> 10 | 11 | Webhook path is '/webhook', therefore: 12 | 13 | <webhook_url>: https://<base>/webhook 14 | """ 15 | 16 | def on_chat_message(msg): 17 | content_type, chat_type, chat_id = telepot.glance(msg) 18 | print('Chat Message:', content_type, chat_type, chat_id) 19 | 20 | def on_callback_query(msg): 21 | query_id, from_id, data = telepot.glance(msg, flavor='callback_query') 22 | print('Callback query:', query_id, from_id, data) 23 | 24 | # need `/setinline` 25 | async def on_inline_query(msg): 26 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 27 | print('Inline Query:', query_id, from_id, query_string) 28 | 29 | # Compose your own answers 30 | articles = [{'type': 'article', 31 | 'id': 'abc', 'title': 'ABC', 'message_text': 'Good morning'}] 32 | 33 | await bot.answerInlineQuery(query_id, articles) 34 | 35 | # need `/setinlinefeedback` 36 | def on_chosen_inline_result(msg): 37 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 38 | print('Chosen Inline Result:', result_id, from_id, query_string) 39 | 40 | async def feeder(request): 41 | data = await request.text() 42 | webhook.feed(data) 43 | return web.Response(body='OK'.encode('utf-8')) 44 | 45 | async def init(app, bot): 46 | app.router.add_route('GET', '/webhook', feeder) 47 | app.router.add_route('POST', '/webhook', feeder) 48 | 49 | await bot.setWebhook(URL) 50 | 51 | 52 | TOKEN = sys.argv[1] 53 | PORT = int(sys.argv[2]) 54 | URL = sys.argv[3] 55 | 56 | loop = asyncio.get_event_loop() 57 | 58 | app = web.Application(loop=loop) 59 | bot = telepot.aio.Bot(TOKEN, loop=loop) 60 | webhook = OrderedWebhook(bot, {'chat': on_chat_message, 61 | 'callback_query': on_callback_query, 62 | 'inline_query': on_inline_query, 63 | 'chosen_inline_result': on_chosen_inline_result}) 64 | 65 | loop.run_until_complete(init(app, bot)) 66 | 67 | loop.create_task(webhook.run_forever()) 68 | 69 | try: 70 | web.run_app(app, port=PORT) 71 | except KeyboardInterrupt: 72 | pass 73 | -------------------------------------------------------------------------------- /examples/webhook/flask_counter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from flask import Flask, request 3 | import telepot 4 | from telepot.loop import OrderedWebhook 5 | from telepot.delegate import per_chat_id, create_open, pave_event_space 6 | 7 | """ 8 | $ python3.5 flask_counter.py <token> <listening_port> <webhook_url> 9 | 10 | Webhook path is '/webhook', therefore: 11 | 12 | <webhook_url>: https://<base>/webhook 13 | """ 14 | 15 | class MessageCounter(telepot.helper.ChatHandler): 16 | def __init__(self, *args, **kwargs): 17 | super(MessageCounter, self).__init__(*args, **kwargs) 18 | self._count = 0 19 | 20 | def on_chat_message(self, msg): 21 | self._count += 1 22 | self.sender.sendMessage(self._count) 23 | 24 | 25 | TOKEN = sys.argv[1] 26 | PORT = int(sys.argv[2]) 27 | URL = sys.argv[3] 28 | 29 | app = Flask(__name__) 30 | 31 | bot = telepot.DelegatorBot(TOKEN, [ 32 | pave_event_space()( 33 | per_chat_id(), create_open, MessageCounter, timeout=10), 34 | ]) 35 | 36 | webhook = OrderedWebhook(bot) 37 | 38 | @app.route('/webhook', methods=['GET', 'POST']) 39 | def pass_update(): 40 | webhook.feed(request.data) 41 | return 'OK' 42 | 43 | if __name__ == '__main__': 44 | try: 45 | bot.setWebhook(URL) 46 | # Sometimes it would raise this error, but webhook still set successfully. 47 | except telepot.exception.TooManyRequestsError: 48 | pass 49 | 50 | webhook.run_as_thread() 51 | app.run(port=PORT, debug=True) 52 | -------------------------------------------------------------------------------- /examples/webhook/flask_skeleton.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from flask import Flask, request 3 | import telepot 4 | from telepot.loop import OrderedWebhook 5 | 6 | """ 7 | $ python2.7 flask_skeleton.py <token> <listening_port> <webhook_url> 8 | 9 | Webhook path is '/webhook', therefore: 10 | 11 | <webhook_url>: https://<base>/webhook 12 | """ 13 | 14 | def on_chat_message(msg): 15 | content_type, chat_type, chat_id = telepot.glance(msg) 16 | print('Chat Message:', content_type, chat_type, chat_id) 17 | 18 | def on_callback_query(msg): 19 | query_id, from_id, data = telepot.glance(msg, flavor='callback_query') 20 | print('Callback query:', query_id, from_id, data) 21 | 22 | # need `/setinline` 23 | def on_inline_query(msg): 24 | query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query') 25 | print('Inline Query:', query_id, from_id, query_string) 26 | 27 | # Compose your own answers 28 | articles = [{'type': 'article', 29 | 'id': 'abc', 'title': 'ABC', 'message_text': 'Good morning'}] 30 | 31 | bot.answerInlineQuery(query_id, articles) 32 | 33 | # need `/setinlinefeedback` 34 | def on_chosen_inline_result(msg): 35 | result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result') 36 | print('Chosen Inline Result:', result_id, from_id, query_string) 37 | 38 | 39 | TOKEN = sys.argv[1] 40 | PORT = int(sys.argv[2]) 41 | URL = sys.argv[3] 42 | 43 | app = Flask(__name__) 44 | bot = telepot.Bot(TOKEN) 45 | webhook = OrderedWebhook(bot, {'chat': on_chat_message, 46 | 'callback_query': on_callback_query, 47 | 'inline_query': on_inline_query, 48 | 'chosen_inline_result': on_chosen_inline_result}) 49 | 50 | @app.route('/webhook', methods=['GET', 'POST']) 51 | def pass_update(): 52 | webhook.feed(request.data) 53 | return 'OK' 54 | 55 | if __name__ == '__main__': 56 | try: 57 | bot.setWebhook(URL) 58 | # Sometimes it would raise this error, but webhook still set successfully. 59 | except telepot.exception.TooManyRequestsError: 60 | pass 61 | 62 | webhook.run_as_thread() 63 | app.run(port=PORT, debug=True) 64 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from setuptools.command.install_lib import install_lib 3 | from setuptools.command.build_py import build_py 4 | from os import path 5 | import sys 6 | import re 7 | 8 | def _not_async(filepath): 9 | return filepath.find('aio/') < 0 10 | 11 | # Do not copy async module for Python 3.3 or below. 12 | class nocopy_async(build_py): 13 | def find_all_modules(self): 14 | modules = build_py.find_all_modules(self) 15 | modules = list(filter(lambda m: _not_async(m[-1]), modules)) 16 | return modules 17 | 18 | def find_package_modules(self, package, package_dir): 19 | modules = build_py.find_package_modules(self, package, package_dir) 20 | modules = list(filter(lambda m: _not_async(m[-1]), modules)) 21 | return modules 22 | 23 | # Do not compile async.py for Python 3.3 or below. 24 | class nocompile_async(install_lib): 25 | def byte_compile(self, files): 26 | files = list(filter(_not_async, files)) 27 | install_lib.byte_compile(self, files) 28 | 29 | 30 | PY_35 = sys.version_info >= (3,5) 31 | 32 | here = path.abspath(path.dirname(__file__)) 33 | 34 | install_requires = ['urllib3>=1.9.1'] 35 | cmdclass = {} 36 | 37 | if PY_35: 38 | # one more dependency for Python 3.5 (async version) 39 | install_requires += ['aiohttp>=3.0.0'] 40 | else: 41 | # do not copy/compile async version for older Python 42 | cmdclass['build_py'] = nocopy_async 43 | cmdclass['install_lib'] = nocompile_async 44 | 45 | # Parse version 46 | with open(path.join(here, 'telepot', '__init__.py')) as f: 47 | m = re.search('^__version_info__ *= *\(([0-9]+), *([0-9]+)\)', f.read(), re.MULTILINE) 48 | version = '.'.join(m.groups()) 49 | 50 | 51 | setup( 52 | cmdclass=cmdclass, 53 | 54 | name='telepot', 55 | packages=['telepot', 'telepot.aio'], 56 | # Do not filter out packages because we need the whole thing during `sdist`. 57 | 58 | install_requires=install_requires, 59 | 60 | version=version, 61 | 62 | description='Python framework for Telegram Bot API', 63 | 64 | long_description='', 65 | 66 | url='https://github.com/nickoala/telepot', 67 | 68 | author='Nick Lee', 69 | author_email='lee1nick@yahoo.ca', 70 | 71 | license='MIT', 72 | 73 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 74 | classifiers=[ 75 | 'Development Status :: 5 - Production/Stable', 76 | 77 | # Indicate who your project is intended for 78 | 'Intended Audience :: Developers', 79 | 'Topic :: Software Development :: Libraries :: Python Modules', 80 | 'Topic :: Communications :: Chat', 81 | 82 | 'License :: OSI Approved :: MIT License', 83 | 84 | 'Programming Language :: Python :: 2.7', 85 | 'Programming Language :: Python :: 3', 86 | 'Programming Language :: Python :: 3.2', 87 | 'Programming Language :: Python :: 3.3', 88 | 'Programming Language :: Python :: 3.4', 89 | 'Programming Language :: Python :: 3.5', 90 | 'Programming Language :: Python :: 3.6', 91 | ], 92 | 93 | keywords='telegram bot api python wrapper', 94 | ) 95 | -------------------------------------------------------------------------------- /telepot/aio/api.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiohttp 3 | import async_timeout 4 | import atexit 5 | import re 6 | import json 7 | from .. import exception 8 | from ..api import _methodurl, _which_pool, _fileurl, _guess_filename 9 | 10 | _loop = asyncio.get_event_loop() 11 | 12 | _pools = { 13 | 'default': aiohttp.ClientSession( 14 | connector=aiohttp.TCPConnector(limit=10), 15 | loop=_loop) 16 | } 17 | 18 | _timeout = 30 19 | _proxy = None # (url, (username, password)) 20 | 21 | def set_proxy(url, basic_auth=None): 22 | global _proxy 23 | if not url: 24 | _proxy = None 25 | else: 26 | _proxy = (url, basic_auth) if basic_auth else (url,) 27 | 28 | def _proxy_kwargs(): 29 | if _proxy is None or len(_proxy) == 0: 30 | return {} 31 | elif len(_proxy) == 1: 32 | return {'proxy': _proxy[0]} 33 | elif len(_proxy) == 2: 34 | return {'proxy': _proxy[0], 'proxy_auth': aiohttp.BasicAuth(*_proxy[1])} 35 | else: 36 | raise RuntimeError("_proxy has invalid length") 37 | 38 | async def _close_pools(): 39 | global _pools 40 | for s in _pools.values(): 41 | await s.close() 42 | 43 | atexit.register(lambda: _loop.create_task(_close_pools())) # have to wrap async function 44 | 45 | def _create_onetime_pool(): 46 | return aiohttp.ClientSession( 47 | connector=aiohttp.TCPConnector(limit=1, force_close=True), 48 | loop=_loop) 49 | 50 | def _default_timeout(req, **user_kw): 51 | return _timeout 52 | 53 | def _compose_timeout(req, **user_kw): 54 | token, method, params, files = req 55 | 56 | if method == 'getUpdates' and params and 'timeout' in params: 57 | # Ensure HTTP timeout is longer than getUpdates timeout 58 | return params['timeout'] + _default_timeout(req, **user_kw) 59 | elif files: 60 | # Disable timeout if uploading files. For some reason, the larger the file, 61 | # the longer it takes for the server to respond (after upload is finished). 62 | # It is unclear how long timeout should be. 63 | return None 64 | else: 65 | return _default_timeout(req, **user_kw) 66 | 67 | def _compose_data(req, **user_kw): 68 | token, method, params, files = req 69 | 70 | data = aiohttp.FormData() 71 | 72 | if params: 73 | for key,value in params.items(): 74 | data.add_field(key, str(value)) 75 | 76 | if files: 77 | for key,f in files.items(): 78 | if isinstance(f, tuple): 79 | if len(f) == 2: 80 | filename, fileobj = f 81 | else: 82 | raise ValueError('Tuple must have exactly 2 elements: filename, fileobj') 83 | else: 84 | filename, fileobj = _guess_filename(f) or key, f 85 | 86 | data.add_field(key, fileobj, filename=filename) 87 | 88 | return data 89 | 90 | def _transform(req, **user_kw): 91 | timeout = _compose_timeout(req, **user_kw) 92 | 93 | data = _compose_data(req, **user_kw) 94 | 95 | url = _methodurl(req, **user_kw) 96 | 97 | name = _which_pool(req, **user_kw) 98 | 99 | if name is None: 100 | session = _create_onetime_pool() 101 | cleanup = session.close # one-time session: remember to close 102 | else: 103 | session = _pools[name] 104 | cleanup = None # reuse: do not close 105 | 106 | kwargs = {'data':data} 107 | kwargs.update(user_kw) 108 | 109 | return session.post, (url,), kwargs, timeout, cleanup 110 | 111 | async def _parse(response): 112 | try: 113 | data = await response.json() 114 | if data is None: 115 | raise ValueError() 116 | except (ValueError, json.JSONDecodeError, aiohttp.ClientResponseError): 117 | text = await response.text() 118 | raise exception.BadHTTPResponse(response.status, text, response) 119 | 120 | if data['ok']: 121 | return data['result'] 122 | else: 123 | description, error_code = data['description'], data['error_code'] 124 | 125 | # Look for specific error ... 126 | for e in exception.TelegramError.__subclasses__(): 127 | n = len(e.DESCRIPTION_PATTERNS) 128 | if any(map(re.search, e.DESCRIPTION_PATTERNS, n*[description], n*[re.IGNORECASE])): 129 | raise e(description, error_code, data) 130 | 131 | # ... or raise generic error 132 | raise exception.TelegramError(description, error_code, data) 133 | 134 | async def request(req, **user_kw): 135 | fn, args, kwargs, timeout, cleanup = _transform(req, **user_kw) 136 | 137 | kwargs.update(_proxy_kwargs()) 138 | try: 139 | if timeout is None: 140 | async with fn(*args, **kwargs) as r: 141 | return await _parse(r) 142 | else: 143 | try: 144 | with async_timeout.timeout(timeout): 145 | async with fn(*args, **kwargs) as r: 146 | return await _parse(r) 147 | 148 | except asyncio.TimeoutError: 149 | raise exception.TelegramError('Response timeout', 504, {}) 150 | 151 | except aiohttp.ClientConnectionError: 152 | raise exception.TelegramError('Connection Error', 400, {}) 153 | 154 | finally: 155 | if cleanup: # e.g. closing one-time session 156 | if asyncio.iscoroutinefunction(cleanup): 157 | await cleanup() 158 | else: 159 | cleanup() 160 | 161 | def download(req): 162 | session = _create_onetime_pool() 163 | 164 | kwargs = {} 165 | kwargs.update(_proxy_kwargs()) 166 | 167 | return session, session.get(_fileurl(req), timeout=_timeout, **kwargs) 168 | # Caller should close session after download is complete 169 | -------------------------------------------------------------------------------- /telepot/aio/delegate.py: -------------------------------------------------------------------------------- 1 | """ 2 | Like :mod:`telepot.delegate`, this module has a bunch of seeder factories 3 | and delegator factories. 4 | 5 | .. autofunction:: per_chat_id 6 | .. autofunction:: per_chat_id_in 7 | .. autofunction:: per_chat_id_except 8 | .. autofunction:: per_from_id 9 | .. autofunction:: per_from_id_in 10 | .. autofunction:: per_from_id_except 11 | .. autofunction:: per_inline_from_id 12 | .. autofunction:: per_inline_from_id_in 13 | .. autofunction:: per_inline_from_id_except 14 | .. autofunction:: per_application 15 | .. autofunction:: per_message 16 | .. autofunction:: per_event_source_id 17 | .. autofunction:: per_callback_query_chat_id 18 | .. autofunction:: per_callback_query_origin 19 | .. autofunction:: per_invoice_payload 20 | .. autofunction:: until 21 | .. autofunction:: chain 22 | .. autofunction:: pair 23 | .. autofunction:: pave_event_space 24 | .. autofunction:: include_callback_query_chat_id 25 | .. autofunction:: intercept_callback_query_origin 26 | """ 27 | 28 | import asyncio 29 | import traceback 30 | from .. import exception 31 | from . import helper 32 | 33 | # Mirror traditional version to avoid having to import one more module 34 | from ..delegate import ( 35 | per_chat_id, per_chat_id_in, per_chat_id_except, 36 | per_from_id, per_from_id_in, per_from_id_except, 37 | per_inline_from_id, per_inline_from_id_in, per_inline_from_id_except, 38 | per_application, per_message, per_event_source_id, 39 | per_callback_query_chat_id, per_callback_query_origin, per_invoice_payload, 40 | until, chain, pair, pave_event_space, 41 | include_callback_query_chat_id, intercept_callback_query_origin 42 | ) 43 | 44 | def _ensure_coroutine_function(fn): 45 | return fn if asyncio.iscoroutinefunction(fn) else asyncio.coroutine(fn) 46 | 47 | def call(corofunc, *args, **kwargs): 48 | """ 49 | :return: 50 | a delegator function that returns a coroutine object by calling 51 | ``corofunc(seed_tuple, *args, **kwargs)``. 52 | """ 53 | corofunc = _ensure_coroutine_function(corofunc) 54 | def f(seed_tuple): 55 | return corofunc(seed_tuple, *args, **kwargs) 56 | return f 57 | 58 | def create_run(cls, *args, **kwargs): 59 | """ 60 | :return: 61 | a delegator function that calls the ``cls`` constructor whose arguments being 62 | a seed tuple followed by supplied ``*args`` and ``**kwargs``, then returns 63 | a coroutine object by calling the object's ``run`` method, which should be 64 | a coroutine function. 65 | """ 66 | def f(seed_tuple): 67 | j = cls(seed_tuple, *args, **kwargs) 68 | return _ensure_coroutine_function(j.run)() 69 | return f 70 | 71 | def create_open(cls, *args, **kwargs): 72 | """ 73 | :return: 74 | a delegator function that calls the ``cls`` constructor whose arguments being 75 | a seed tuple followed by supplied ``*args`` and ``**kwargs``, then returns 76 | a looping coroutine object that uses the object's ``listener`` to wait for 77 | messages and invokes instance method ``open``, ``on_message``, and ``on_close`` 78 | accordingly. 79 | """ 80 | def f(seed_tuple): 81 | j = cls(seed_tuple, *args, **kwargs) 82 | 83 | async def wait_loop(): 84 | bot, msg, seed = seed_tuple 85 | try: 86 | handled = await helper._invoke(j.open, msg, seed) 87 | if not handled: 88 | await helper._invoke(j.on_message, msg) 89 | 90 | while 1: 91 | msg = await j.listener.wait() 92 | await helper._invoke(j.on_message, msg) 93 | 94 | # These exceptions are "normal" exits. 95 | except (exception.IdleTerminate, exception.StopListening) as e: 96 | await helper._invoke(j.on_close, e) 97 | 98 | # Any other exceptions are accidents. **Print it out.** 99 | # This is to prevent swallowing exceptions in the case that on_close() 100 | # gets overridden but fails to account for unexpected exceptions. 101 | except Exception as e: 102 | traceback.print_exc() 103 | await helper._invoke(j.on_close, e) 104 | 105 | return wait_loop() 106 | return f 107 | -------------------------------------------------------------------------------- /telepot/aio/hack.py: -------------------------------------------------------------------------------- 1 | try: 2 | import aiohttp 3 | from urllib.parse import quote 4 | 5 | def content_disposition_header(disptype, quote_fields=True, **params): 6 | if not disptype or not (aiohttp.helpers.TOKEN > set(disptype)): 7 | raise ValueError('bad content disposition type {!r}' 8 | ''.format(disptype)) 9 | 10 | value = disptype 11 | if params: 12 | lparams = [] 13 | for key, val in params.items(): 14 | if not key or not (aiohttp.helpers.TOKEN > set(key)): 15 | raise ValueError('bad content disposition parameter' 16 | ' {!r}={!r}'.format(key, val)) 17 | 18 | ###### Do not encode filename 19 | if key == 'filename': 20 | qval = val 21 | else: 22 | qval = quote(val, '') if quote_fields else val 23 | 24 | lparams.append((key, '"%s"' % qval)) 25 | 26 | sparams = '; '.join('='.join(pair) for pair in lparams) 27 | value = '; '.join((value, sparams)) 28 | return value 29 | 30 | # Override original version 31 | aiohttp.payload.content_disposition_header = content_disposition_header 32 | 33 | # In case aiohttp changes and this hack no longer works, I don't want it to 34 | # bog down the entire library. 35 | except (ImportError, AttributeError): 36 | pass 37 | -------------------------------------------------------------------------------- /telepot/aio/routing.py: -------------------------------------------------------------------------------- 1 | from .helper import _create_invoker 2 | from .. import all_content_types 3 | 4 | # Mirror traditional version to avoid having to import one more module 5 | from ..routing import ( 6 | by_content_type, by_command, by_chat_command, by_text, by_data, by_regex, 7 | process_key, lower_key, upper_key 8 | ) 9 | 10 | def make_routing_table(obj, keys, prefix='on_'): 11 | """ 12 | :return: 13 | a dictionary roughly equivalent to ``{'key1': obj.on_key1, 'key2': obj.on_key2, ...}``, 14 | but ``obj`` does not have to define all methods. It may define the needed ones only. 15 | 16 | :param obj: the object 17 | 18 | :param keys: a list of keys 19 | 20 | :param prefix: a string to be prepended to keys to make method names 21 | """ 22 | def maptuple(k): 23 | if isinstance(k, tuple): 24 | if len(k) == 2: 25 | return k 26 | elif len(k) == 1: 27 | return k[0], _create_invoker(obj, prefix+k[0]) 28 | else: 29 | raise ValueError() 30 | else: 31 | return k, _create_invoker(obj, prefix+k) 32 | 33 | return dict([maptuple(k) for k in keys]) 34 | 35 | def make_content_type_routing_table(obj, prefix='on_'): 36 | """ 37 | :return: 38 | a dictionary covering all available content types, roughly equivalent to 39 | ``{'text': obj.on_text, 'photo': obj.on_photo, ...}``, 40 | but ``obj`` does not have to define all methods. It may define the needed ones only. 41 | 42 | :param obj: the object 43 | 44 | :param prefix: a string to be prepended to content types to make method names 45 | """ 46 | return make_routing_table(obj, all_content_types, prefix) 47 | -------------------------------------------------------------------------------- /telepot/api.py: -------------------------------------------------------------------------------- 1 | import urllib3 2 | import logging 3 | import json 4 | import re 5 | import os 6 | 7 | from . import exception, _isstring 8 | 9 | # Suppress InsecurePlatformWarning 10 | urllib3.disable_warnings() 11 | 12 | 13 | _default_pool_params = dict(num_pools=3, maxsize=10, retries=3, timeout=30) 14 | _onetime_pool_params = dict(num_pools=1, maxsize=1, retries=3, timeout=30) 15 | 16 | _pools = { 17 | 'default': urllib3.PoolManager(**_default_pool_params), 18 | } 19 | 20 | _onetime_pool_spec = (urllib3.PoolManager, _onetime_pool_params) 21 | 22 | 23 | def set_proxy(url, basic_auth=None): 24 | """ 25 | Access Bot API through a proxy. 26 | 27 | :param url: proxy URL 28 | :param basic_auth: 2-tuple ``('username', 'password')`` 29 | """ 30 | global _pools, _onetime_pool_spec 31 | if not url: 32 | _pools['default'] = urllib3.PoolManager(**_default_pool_params) 33 | _onetime_pool_spec = (urllib3.PoolManager, _onetime_pool_params) 34 | elif basic_auth: 35 | h = urllib3.make_headers(proxy_basic_auth=':'.join(basic_auth)) 36 | _pools['default'] = urllib3.ProxyManager(url, proxy_headers=h, **_default_pool_params) 37 | _onetime_pool_spec = (urllib3.ProxyManager, dict(proxy_url=url, proxy_headers=h, **_onetime_pool_params)) 38 | else: 39 | _pools['default'] = urllib3.ProxyManager(url, **_default_pool_params) 40 | _onetime_pool_spec = (urllib3.ProxyManager, dict(proxy_url=url, **_onetime_pool_params)) 41 | 42 | def _create_onetime_pool(): 43 | cls, kw = _onetime_pool_spec 44 | return cls(**kw) 45 | 46 | def _methodurl(req, **user_kw): 47 | token, method, params, files = req 48 | return 'https://api.telegram.org/bot%s/%s' % (token, method) 49 | 50 | def _which_pool(req, **user_kw): 51 | token, method, params, files = req 52 | return None if files else 'default' 53 | 54 | def _guess_filename(obj): 55 | name = getattr(obj, 'name', None) 56 | if name and _isstring(name) and name[0] != '<' and name[-1] != '>': 57 | return os.path.basename(name) 58 | 59 | def _filetuple(key, f): 60 | if not isinstance(f, tuple): 61 | return (_guess_filename(f) or key, f.read()) 62 | elif len(f) == 1: 63 | return (_guess_filename(f[0]) or key, f[0].read()) 64 | elif len(f) == 2: 65 | return (f[0], f[1].read()) 66 | elif len(f) == 3: 67 | return (f[0], f[1].read(), f[2]) 68 | else: 69 | raise ValueError() 70 | 71 | import sys 72 | PY_3 = sys.version_info.major >= 3 73 | def _fix_type(v): 74 | if isinstance(v, float if PY_3 else (long, float)): 75 | return str(v) 76 | else: 77 | return v 78 | 79 | def _compose_fields(req, **user_kw): 80 | token, method, params, files = req 81 | 82 | fields = {k:_fix_type(v) for k,v in params.items()} if params is not None else {} 83 | if files: 84 | fields.update({k:_filetuple(k,v) for k,v in files.items()}) 85 | 86 | return fields 87 | 88 | def _default_timeout(req, **user_kw): 89 | name = _which_pool(req, **user_kw) 90 | if name is None: 91 | return _onetime_pool_spec[1]['timeout'] 92 | else: 93 | return _pools[name].connection_pool_kw['timeout'] 94 | 95 | def _compose_kwargs(req, **user_kw): 96 | token, method, params, files = req 97 | kw = {} 98 | 99 | if not params and not files: 100 | kw['encode_multipart'] = False 101 | 102 | if method == 'getUpdates' and params and 'timeout' in params: 103 | # Ensure HTTP timeout is longer than getUpdates timeout 104 | kw['timeout'] = params['timeout'] + _default_timeout(req, **user_kw) 105 | elif files: 106 | # Disable timeout if uploading files. For some reason, the larger the file, 107 | # the longer it takes for the server to respond (after upload is finished). 108 | # It is unclear how long timeout should be. 109 | kw['timeout'] = None 110 | 111 | # Let user-supplied arguments override 112 | kw.update(user_kw) 113 | return kw 114 | 115 | def _transform(req, **user_kw): 116 | kwargs = _compose_kwargs(req, **user_kw) 117 | 118 | fields = _compose_fields(req, **user_kw) 119 | 120 | url = _methodurl(req, **user_kw) 121 | 122 | name = _which_pool(req, **user_kw) 123 | 124 | if name is None: 125 | pool = _create_onetime_pool() 126 | else: 127 | pool = _pools[name] 128 | 129 | return pool.request_encode_body, ('POST', url, fields), kwargs 130 | 131 | def _parse(response): 132 | try: 133 | text = response.data.decode('utf-8') 134 | data = json.loads(text) 135 | except ValueError: # No JSON object could be decoded 136 | raise exception.BadHTTPResponse(response.status, text, response) 137 | 138 | if data['ok']: 139 | return data['result'] 140 | else: 141 | description, error_code = data['description'], data['error_code'] 142 | 143 | # Look for specific error ... 144 | for e in exception.TelegramError.__subclasses__(): 145 | n = len(e.DESCRIPTION_PATTERNS) 146 | if any(map(re.search, e.DESCRIPTION_PATTERNS, n*[description], n*[re.IGNORECASE])): 147 | raise e(description, error_code, data) 148 | 149 | # ... or raise generic error 150 | raise exception.TelegramError(description, error_code, data) 151 | 152 | def request(req, **user_kw): 153 | fn, args, kwargs = _transform(req, **user_kw) 154 | r = fn(*args, **kwargs) # `fn` must be thread-safe 155 | return _parse(r) 156 | 157 | def _fileurl(req): 158 | token, path = req 159 | return 'https://api.telegram.org/file/bot%s/%s' % (token, path) 160 | 161 | def download(req, **user_kw): 162 | pool = _create_onetime_pool() 163 | r = pool.request('GET', _fileurl(req), **user_kw) 164 | return r 165 | -------------------------------------------------------------------------------- /telepot/exception.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class TelepotException(Exception): 4 | """ Base class of following exceptions. """ 5 | pass 6 | 7 | class BadFlavor(TelepotException): 8 | def __init__(self, offender): 9 | super(BadFlavor, self).__init__(offender) 10 | 11 | @property 12 | def offender(self): 13 | return self.args[0] 14 | 15 | PY_3 = sys.version_info.major >= 3 16 | 17 | class BadHTTPResponse(TelepotException): 18 | """ 19 | All requests to Bot API should result in a JSON response. If non-JSON, this 20 | exception is raised. While it is hard to pinpoint exactly when this might happen, 21 | the following situations have been observed to give rise to it: 22 | 23 | - an unreasonable token, e.g. ``abc``, ``123``, anything that does not even 24 | remotely resemble a correct token. 25 | - a bad gateway, e.g. when Telegram servers are down. 26 | """ 27 | 28 | def __init__(self, status, text, response): 29 | super(BadHTTPResponse, self).__init__(status, text, response) 30 | 31 | @property 32 | def status(self): 33 | return self.args[0] 34 | 35 | @property 36 | def text(self): 37 | return self.args[1] 38 | 39 | @property 40 | def response(self): 41 | return self.args[2] 42 | 43 | class EventNotFound(TelepotException): 44 | def __init__(self, event): 45 | super(EventNotFound, self).__init__(event) 46 | 47 | @property 48 | def event(self): 49 | return self.args[0] 50 | 51 | class WaitTooLong(TelepotException): 52 | def __init__(self, seconds): 53 | super(WaitTooLong, self).__init__(seconds) 54 | 55 | @property 56 | def seconds(self): 57 | return self.args[0] 58 | 59 | class IdleTerminate(WaitTooLong): 60 | pass 61 | 62 | class StopListening(TelepotException): 63 | pass 64 | 65 | class TelegramError(TelepotException): 66 | """ 67 | To indicate erroneous situations, Telegram returns a JSON object containing 68 | an *error code* and a *description*. This will cause a ``TelegramError`` to 69 | be raised. Before raising a generic ``TelegramError``, telepot looks for 70 | a more specific subclass that "matches" the error. If such a class exists, 71 | an exception of that specific subclass is raised. This allows you to either 72 | catch specific errors or to cast a wide net (by a catch-all ``TelegramError``). 73 | This also allows you to incorporate custom ``TelegramError`` easily. 74 | 75 | Subclasses must define a class variable ``DESCRIPTION_PATTERNS`` which is a list 76 | of regular expressions. If an error's *description* matches any of the regular expressions, 77 | an exception of that subclass is raised. 78 | """ 79 | 80 | def __init__(self, description, error_code, json): 81 | super(TelegramError, self).__init__(description, error_code, json) 82 | 83 | @property 84 | def description(self): 85 | return self.args[0] 86 | 87 | @property 88 | def error_code(self): 89 | return self.args[1] 90 | 91 | @property 92 | def json(self): 93 | return self.args[2] 94 | 95 | class UnauthorizedError(TelegramError): 96 | DESCRIPTION_PATTERNS = ['unauthorized'] 97 | 98 | class BotWasKickedError(TelegramError): 99 | DESCRIPTION_PATTERNS = ['bot.*kicked'] 100 | 101 | class BotWasBlockedError(TelegramError): 102 | DESCRIPTION_PATTERNS = ['bot.*blocked'] 103 | 104 | class TooManyRequestsError(TelegramError): 105 | DESCRIPTION_PATTERNS = ['too *many *requests'] 106 | 107 | class MigratedToSupergroupChatError(TelegramError): 108 | DESCRIPTION_PATTERNS = ['migrated.*supergroup *chat'] 109 | 110 | class NotEnoughRightsError(TelegramError): 111 | DESCRIPTION_PATTERNS = ['not *enough *rights'] 112 | -------------------------------------------------------------------------------- /telepot/filtering.py: -------------------------------------------------------------------------------- 1 | def pick(obj, keys): 2 | def pick1(k): 3 | if type(obj) is dict: 4 | return obj[k] 5 | else: 6 | return getattr(obj, k) 7 | 8 | if isinstance(keys, list): 9 | return [pick1(k) for k in keys] 10 | else: 11 | return pick1(keys) 12 | 13 | def match(data, template): 14 | if isinstance(template, dict) and isinstance(data, dict): 15 | def pick_and_match(kv): 16 | template_key, template_value = kv 17 | if hasattr(template_key, 'search'): # regex 18 | data_keys = list(filter(template_key.search, data.keys())) 19 | if not data_keys: 20 | return False 21 | elif template_key in data: 22 | data_keys = [template_key] 23 | else: 24 | return False 25 | return any(map(lambda data_value: match(data_value, template_value), pick(data, data_keys))) 26 | 27 | return all(map(pick_and_match, template.items())) 28 | elif callable(template): 29 | return template(data) 30 | else: 31 | return data == template 32 | 33 | def match_all(msg, templates): 34 | return all(map(lambda t: match(msg, t), templates)) 35 | -------------------------------------------------------------------------------- /telepot/hack.py: -------------------------------------------------------------------------------- 1 | try: 2 | import urllib3.fields 3 | 4 | # Do not encode unicode filename, so Telegram servers understand it. 5 | def _noencode_filename(fn): 6 | def w(name, value): 7 | if name == 'filename': 8 | return '%s="%s"' % (name, value) 9 | else: 10 | return fn(name, value) 11 | return w 12 | 13 | urllib3.fields.format_header_param = _noencode_filename(urllib3.fields.format_header_param) 14 | 15 | except (ImportError, AttributeError): 16 | pass 17 | -------------------------------------------------------------------------------- /telepot/text.py: -------------------------------------------------------------------------------- 1 | def _apply_entities(text, entities, escape_map, format_map): 2 | def inside_entities(i): 3 | return any(map(lambda e: 4 | e['offset'] <= i < e['offset']+e['length'], 5 | entities)) 6 | 7 | # Split string into char sequence and escape in-place to 8 | # preserve index positions. 9 | seq = list(map(lambda c,i: 10 | escape_map[c] # escape special characters 11 | if c in escape_map and not inside_entities(i) 12 | else c, 13 | list(text), # split string to char sequence 14 | range(0,len(text)))) # along with each char's index 15 | 16 | # Ensure smaller offsets come first 17 | sorted_entities = sorted(entities, key=lambda e: e['offset']) 18 | offset = 0 19 | result = '' 20 | 21 | for e in sorted_entities: 22 | f,n,t = e['offset'], e['length'], e['type'] 23 | 24 | result += ''.join(seq[offset:f]) 25 | 26 | if t in format_map: 27 | # apply format 28 | result += format_map[t](''.join(seq[f:f+n]), e) 29 | else: 30 | result += ''.join(seq[f:f+n]) 31 | 32 | offset = f + n 33 | 34 | result += ''.join(seq[offset:]) 35 | return result 36 | 37 | 38 | def apply_entities_as_markdown(text, entities): 39 | """ 40 | Format text as Markdown. Also take care of escaping special characters. 41 | Returned value can be passed to :meth:`.Bot.sendMessage` with appropriate 42 | ``parse_mode``. 43 | 44 | :param text: 45 | plain text 46 | 47 | :param entities: 48 | a list of `MessageEntity <https://core.telegram.org/bots/api#messageentity>`_ objects 49 | """ 50 | escapes = {'*': '\\*', 51 | '_': '\\_', 52 | '[': '\\[', 53 | '`': '\\`',} 54 | 55 | formatters = {'bold': lambda s,e: '*'+s+'*', 56 | 'italic': lambda s,e: '_'+s+'_', 57 | 'text_link': lambda s,e: '['+s+']('+e['url']+')', 58 | 'text_mention': lambda s,e: '['+s+'](tg://user?id='+str(e['user']['id'])+')', 59 | 'code': lambda s,e: '`'+s+'`', 60 | 'pre': lambda s,e: '```text\n'+s+'```'} 61 | 62 | return _apply_entities(text, entities, escapes, formatters) 63 | 64 | 65 | def apply_entities_as_html(text, entities): 66 | """ 67 | Format text as HTML. Also take care of escaping special characters. 68 | Returned value can be passed to :meth:`.Bot.sendMessage` with appropriate 69 | ``parse_mode``. 70 | 71 | :param text: 72 | plain text 73 | 74 | :param entities: 75 | a list of `MessageEntity <https://core.telegram.org/bots/api#messageentity>`_ objects 76 | """ 77 | escapes = {'<': '<', 78 | '>': '>', 79 | '&': '&',} 80 | 81 | formatters = {'bold': lambda s,e: '<b>'+s+'</b>', 82 | 'italic': lambda s,e: '<i>'+s+'</i>', 83 | 'text_link': lambda s,e: '<a href="'+e['url']+'">'+s+'</a>', 84 | 'text_mention': lambda s,e: '<a href="tg://user?id='+str(e['user']['id'])+'">'+s+'</a>', 85 | 'code': lambda s,e: '<code>'+s+'</code>', 86 | 'pre': lambda s,e: '<pre>'+s+'</pre>'} 87 | 88 | return _apply_entities(text, entities, escapes, formatters) 89 | -------------------------------------------------------------------------------- /test/bookshelf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/bookshelf.jpg -------------------------------------------------------------------------------- /test/dgdg.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/dgdg.mp3 -------------------------------------------------------------------------------- /test/document.txt: -------------------------------------------------------------------------------- 1 | Something -------------------------------------------------------------------------------- /test/example.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/example.ogg -------------------------------------------------------------------------------- /test/gandhi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/gandhi.png -------------------------------------------------------------------------------- /test/hktraffic.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/hktraffic.mp4 -------------------------------------------------------------------------------- /test/lighthouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/lighthouse.jpg -------------------------------------------------------------------------------- /test/lincoln.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/lincoln.png -------------------------------------------------------------------------------- /test/saturn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickoala/telepot/4bfe4eeb5e48b40e72976ee085a1b0a941ef3cf2/test/saturn.jpg -------------------------------------------------------------------------------- /test/test27_admin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | import telepot.namedtuple 5 | from telepot.routing import by_content_type, make_content_type_routing_table 6 | from telepot.exception import NotEnoughRightsError 7 | 8 | class AdminBot(telepot.Bot): 9 | def on_chat_message(self, msg): 10 | content_type, chat_type, chat_id = telepot.glance(msg) 11 | 12 | if 'edit_date' not in msg: 13 | self.sendMessage(chat_id, 'Edit the message, please.') 14 | else: 15 | self.sendMessage(chat_id, 'Add me to a group, please.') 16 | r = telepot.helper.Router(by_content_type(), make_content_type_routing_table(self)) 17 | self._router.routing_table['chat'] = r.route 18 | 19 | def on_new_chat_member(self, msg, new_chat_member): 20 | print 'New chat member:', new_chat_member 21 | content_type, chat_type, chat_id = telepot.glance(msg) 22 | 23 | r = self.getChat(chat_id) 24 | print r 25 | 26 | r = self.getChatAdministrators(chat_id) 27 | print r 28 | print telepot.namedtuple.ChatMemberArray(r) 29 | 30 | r = self.getChatMembersCount(chat_id) 31 | print r 32 | 33 | while 1: 34 | try: 35 | self.setChatTitle(chat_id, 'AdminBot Title') 36 | print 'Set title successfully.' 37 | break 38 | except NotEnoughRightsError: 39 | print 'No right to set title. Try again in 10 seconds ...' 40 | time.sleep(10) 41 | 42 | while 1: 43 | try: 44 | self.setChatPhoto(chat_id, open('gandhi.png', 'rb')) 45 | print 'Set photo successfully.' 46 | time.sleep(2) # let tester see photo briefly 47 | break 48 | except NotEnoughRightsError: 49 | print 'No right to set photo. Try again in 10 seconds ...' 50 | time.sleep(10) 51 | 52 | while 1: 53 | try: 54 | self.deleteChatPhoto(chat_id) 55 | print 'Delete photo successfully.' 56 | break 57 | except NotEnoughRightsError: 58 | print 'No right to delete photo. Try again in 10 seconds ...' 59 | time.sleep(10) 60 | 61 | print 'I am done. You may remove me from the group.' 62 | 63 | 64 | TOKEN = sys.argv[1] 65 | 66 | bot = AdminBot(TOKEN) 67 | bot.message_loop() 68 | print 'Send me a text message ...' 69 | 70 | while 1: 71 | time.sleep(1) 72 | -------------------------------------------------------------------------------- /test/test27_inline.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import time 4 | import threading 5 | import pprint 6 | import sys 7 | import traceback 8 | import random 9 | import telepot 10 | from telepot.namedtuple import ( 11 | InlineQuery, ChosenInlineResult, InputTextMessageContent, 12 | InlineQueryResultArticle, InlineQueryResultPhoto, InlineQueryResultGame) 13 | 14 | def equivalent(data, nt): 15 | if type(data) is dict: 16 | keys = data.keys() 17 | 18 | # number of dictionary keys == number of non-None values in namedtuple? 19 | if len(keys) != len([f for f in nt._fields if getattr(nt, f) is not None]): 20 | return False 21 | 22 | # map `from` to `from_` 23 | fields = list(map(lambda k: k+'_' if k in ['from'] else k, keys)) 24 | 25 | return all(map(equivalent, [data[k] for k in keys], [getattr(nt, f) for f in fields])) 26 | elif type(data) is list: 27 | return all(map(equivalent, data, nt)) 28 | else: 29 | return data==nt 30 | 31 | def examine(result, type): 32 | try: 33 | print 'Examining %s ......' % type 34 | 35 | nt = type(**result) 36 | assert equivalent(result, nt), 'Not equivalent:::::::::::::::\n%s\n::::::::::::::::\n%s' % (result, nt) 37 | 38 | pprint.pprint(result) 39 | pprint.pprint(nt) 40 | print 41 | except AssertionError: 42 | traceback.print_exc() 43 | answer = raw_input('Do you want to continue? [y] ') 44 | if answer != 'y': 45 | exit(1) 46 | 47 | 48 | def on_inline_query(msg): 49 | def compute(): 50 | articles = [InlineQueryResultArticle( 51 | id='abc', title='HK', input_message_content=InputTextMessageContent(message_text='Hong Kong'), url='https://www.google.com', hide_url=True), 52 | {'type': 'article', 53 | 'id': 'def', 'title': 'SZ', 'input_message_content': {'message_text': 'Shenzhen'}, 'url': 'https://www.yahoo.com'}] 54 | 55 | photos = [InlineQueryResultPhoto( 56 | id='123', photo_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf', thumb_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf'), 57 | {'type': 'photo', 58 | 'id': '345', 'photo_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'thumb_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'caption': 'Caption', 'title': 'Title', 'input_message_content': {'message_text': 'Shenzhen'}}] 59 | 60 | games = [InlineQueryResultGame( 61 | id='abc', game_short_name='sunchaser')] 62 | 63 | results = random.choice([articles, photos, games]) 64 | return results 65 | 66 | query_id, from_id, query = telepot.glance(msg, flavor='inline_query') 67 | 68 | if from_id != USER_ID: 69 | print 'Unauthorized user:', from_id 70 | return 71 | 72 | examine(msg, InlineQuery) 73 | answerer.answer(msg, compute) 74 | 75 | 76 | def on_chosen_inline_result(msg): 77 | result_id, from_id, query = telepot.glance(msg, flavor='chosen_inline_result') 78 | 79 | if from_id != USER_ID: 80 | print 'Unauthorized user:', from_id 81 | return 82 | 83 | examine(msg, ChosenInlineResult) 84 | 85 | print 'Chosen inline query:' 86 | pprint.pprint(msg) 87 | 88 | 89 | TOKEN = sys.argv[1] 90 | USER_ID = long(sys.argv[2]) 91 | 92 | bot = telepot.Bot(TOKEN) 93 | answerer = telepot.helper.Answerer(bot) 94 | 95 | bot.sendMessage(USER_ID, 'Please give me an inline query.') 96 | 97 | bot.message_loop({'inline_query': on_inline_query, 98 | 'chosen_inline_result': on_chosen_inline_result}, run_forever=True) 99 | -------------------------------------------------------------------------------- /test/test27_pay.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from pprint import pprint 4 | import telepot 5 | from telepot.namedtuple import ( 6 | LabeledPrice, Invoice, PreCheckoutQuery, ShippingQuery, ShippingOption, 7 | SuccessfulPayment) 8 | from telepot.loop import MessageLoop 9 | 10 | """ 11 | This script tests the payment process: 12 | 1. Send an invoice 13 | 2. Receive a shipping query, respond with answerShippingQuery() 14 | 3. Receive a pre-checkout query, respond with answerPreCheckoutQuery() 15 | 4. Receive a successful payment 16 | 17 | Run it by: 18 | $ python2.7 script.py <bot-token> <payment-provider-token> 19 | """ 20 | 21 | def on_chat_message(msg): 22 | content_type, chat_type, chat_id = telepot.glance(msg) 23 | print content_type, chat_type, chat_id 24 | 25 | if content_type != 'successful_payment': 26 | sent = bot.sendInvoice( 27 | chat_id, "Nick's Hand Cream", "Keep a man's hand like a woman's", 28 | payload='a-string-identifying-related-payment-messages-tuvwxyz', 29 | provider_token=PAYMENT_PROVIDER_TOKEN, 30 | start_parameter='abc', 31 | currency='HKD', prices=[ 32 | LabeledPrice(label='One Case', amount=987), 33 | LabeledPrice(label='Package', amount=12)], 34 | need_shipping_address=True, is_flexible=True) # required for shipping query 35 | # 'Pay' button appears automatically 36 | 37 | pprint(sent) 38 | print Invoice(**sent['invoice']) 39 | 40 | else: 41 | print 'Successful payment RECEIVED!!!' 42 | pprint(msg) 43 | print SuccessfulPayment(**msg['successful_payment']) 44 | 45 | def on_shipping_query(msg): 46 | query_id, from_id, invoice_payload = telepot.glance(msg, flavor='shipping_query') 47 | 48 | print 'Shipping query:' 49 | print query_id, from_id, invoice_payload 50 | pprint(msg) 51 | print ShippingQuery(**msg) 52 | 53 | bot.answerShippingQuery( 54 | query_id, True, 55 | shipping_options=[ 56 | ShippingOption(id='fedex', title='FedEx', prices=[ 57 | LabeledPrice(label='Local', amount=345), 58 | LabeledPrice(label='International', amount=2345)]), 59 | ShippingOption(id='dhl', title='DHL', prices=[ 60 | LabeledPrice(label='Local', amount=342), 61 | LabeledPrice(label='International', amount=1234)])]) 62 | 63 | def on_pre_checkout_query(msg): 64 | query_id, from_id, invoice_payload, currency, total_amount = telepot.glance(msg, flavor='pre_checkout_query', long=True) 65 | 66 | print 'Pre-Checkout query:' 67 | print query_id, from_id, invoice_payload, currency, total_amount 68 | pprint(msg) 69 | print PreCheckoutQuery(**msg) 70 | 71 | bot.answerPreCheckoutQuery(query_id, True) 72 | 73 | TOKEN = sys.argv[1] 74 | PAYMENT_PROVIDER_TOKEN = sys.argv[2] 75 | 76 | bot = telepot.Bot(TOKEN) 77 | MessageLoop(bot, {'chat': on_chat_message, 78 | 'shipping_query': on_shipping_query, 79 | 'pre_checkout_query': on_pre_checkout_query}).run_as_thread() 80 | 81 | while 1: 82 | time.sleep(10) 83 | -------------------------------------------------------------------------------- /test/test27_queue.py: -------------------------------------------------------------------------------- 1 | import time 2 | import telepot 3 | from telepot.loop import OrderedWebhook 4 | 5 | def u(update_id): 6 | return { 'update_id': update_id, 'message': update_id } 7 | 8 | sequence = [ 9 | u(1), # initialize 10 | u(2), # no buffering 11 | 12 | u(4), # 1-gap 13 | u(3), # clear 2 14 | 15 | u(7), # 2-gap 16 | u(5), # return, leave 1-gap 17 | u(6), # clear 2 18 | 19 | u(10), # 2-gap 20 | u(9), # 1-gap 21 | u(8), # clear 3 22 | 23 | u(15), 24 | u(12), 25 | u(13), 26 | u(11), 27 | u(14), 28 | 29 | u(17), 30 | u(18), 31 | u(21), 32 | u(20), 33 | u(19), 34 | u(16), 35 | 36 | u(22), # no buffering 37 | 38 | u(24), 39 | 9, # skip id=23 40 | 41 | u(23), # discard 42 | 43 | u(26), 44 | u(27), 45 | 9, # skip id=25 46 | 47 | u(25), # discard 48 | 49 | u(30), 50 | u(29), 51 | 5, 52 | u(32), 53 | u(33), 54 | 2, # clear 29,30, skip 28 55 | u(31), # clear 31,32,33 56 | 57 | u(39), 58 | u(36), 59 | 2, 60 | u(37), 61 | 7, # clear 36,37,39 62 | 63 | u(28), # discard 64 | u(38), # discard 65 | 66 | u(40), # return 67 | ] 68 | 69 | def handle(msg): 70 | print msg 71 | 72 | bot = telepot.Bot('abc') 73 | webhook = OrderedWebhook(bot, handle) 74 | 75 | webhook.run_as_thread(maxhold=8) 76 | 77 | for update in sequence: 78 | if type(update) is dict: 79 | webhook.feed(update) 80 | time.sleep(1) 81 | else: 82 | time.sleep(update) 83 | -------------------------------------------------------------------------------- /test/test27_routing.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import random 4 | import telepot.helper 5 | from telepot.routing import (by_content_type, make_content_type_routing_table, 6 | lower_key, by_chat_command, make_routing_table, 7 | by_regex) 8 | 9 | def random_key(msg): 10 | return random.choice([ 11 | 0, 12 | (1,), 13 | (2, ('a1',)), 14 | (3, ('a1', 'a2'), {'b1': 'b'}), 15 | (4, (), {'kw4': 4444, 'kw5': 'xyz'}), 16 | ((None,), ()), 17 | ]) 18 | 19 | def zero(msg): 20 | print 'Zero' 21 | 22 | def one(msg): 23 | print 'One' 24 | 25 | def two(msg, a1): 26 | print 'Two', a1 27 | 28 | def three(msg, a1, a2, b1): 29 | print 'Three', a1, a2, b1 30 | 31 | def none_tuple(msg): 32 | print 'None tuple' 33 | 34 | def none_of_above(msg, *args, **kwargs): 35 | print 'None of above', msg, args, kwargs 36 | 37 | top_router = telepot.helper.Router(random_key, {0: zero, 38 | 1: one, 39 | 2: two, 40 | 3: three, 41 | (None,): none_tuple, 42 | None: none_of_above}) 43 | 44 | for i in range(0,20): 45 | top_router.route({}) 46 | print 47 | 48 | 49 | class ContentTypeHandler(object): 50 | def on_text(self, msg, text): 51 | print 'Text', msg, text 52 | 53 | def on_photo(self, msg, photo): 54 | print 'Photo', msg, photo 55 | 56 | def make_message_like(mm): 57 | for d in mm: 58 | d.update({'chat': {'type': 'private', 'id': 1000}}) 59 | 60 | top_router.key_function = by_content_type() 61 | top_router.routing_table = make_content_type_routing_table(ContentTypeHandler()) 62 | del top_router.routing_table['video'] # let video fall to default handler 63 | top_router.routing_table[None] = none_of_above 64 | 65 | messages = [{'text': 'abc'}, 66 | {'photo': 'some photo'}, 67 | {'video': 'some video'},] 68 | make_message_like(messages) 69 | 70 | for i in range(0,10): 71 | top_router.route(random.choice(messages)) 72 | print 73 | 74 | 75 | class CommandHandler(object): 76 | def on_start(self, msg): 77 | print 'Command: start', msg 78 | 79 | def on_settings(self, msg): 80 | print 'Command: settings', msg 81 | 82 | def on_invalid_text(self, msg): 83 | print 'Invalid text', msg 84 | 85 | def on_invalid_command(self, msg): 86 | print 'Invalid command', msg 87 | 88 | command_handler = CommandHandler() 89 | command_router = telepot.helper.Router(lower_key(by_chat_command()), 90 | make_routing_table(command_handler, [ 91 | 'start', 92 | 'settings', 93 | ((None,), command_handler.on_invalid_text), 94 | (None, command_handler.on_invalid_command), 95 | ])) 96 | 97 | top_router.routing_table['text'] = command_router.route 98 | 99 | messages = [{'text': '/start'}, 100 | {'text': '/SETTINGS'}, 101 | {'text': '/bad'}, 102 | {'text': 'plain text'}, 103 | {'photo': 'some photo'}, 104 | {'video': 'some video'},] 105 | make_message_like(messages) 106 | 107 | for i in range(0,20): 108 | top_router.route(random.choice(messages)) 109 | print 110 | 111 | 112 | class RegexHandler(object): 113 | def on_CS101(self, msg, match): 114 | print 'Someone mentioned CS101 !!!', msg, match.groups() 115 | 116 | def on_CS202(self, msg, match): 117 | print 'Someone mentioned CS202 !!!', msg, match.groups() 118 | 119 | def no_cs_courses_mentioned(self, msg): 120 | print 'No CS courses mentioned ...', msg 121 | 122 | def course_not_exist(self, msg, match): 123 | print '%s does not exist' % match.group(1), msg 124 | 125 | regex_handler = RegexHandler() 126 | regex_router = telepot.helper.Router(by_regex(lambda msg: msg['text'], '(CS[0-9]{3})'), 127 | make_routing_table(regex_handler, [ 128 | 'CS101', 129 | 'CS202', 130 | ((None,), regex_handler.no_cs_courses_mentioned), 131 | (None, regex_handler.course_not_exist), 132 | ])) 133 | 134 | command_router.routing_table[(None,)] = regex_router.route 135 | 136 | messages = [{'text': '/start'}, 137 | {'text': '/SETTINGS'}, 138 | {'text': '/bad'}, 139 | {'text': 'plain text'}, 140 | {'text': 'I want to take CS101.'}, 141 | {'text': 'I\'d rather take CS202.'}, 142 | {'text': 'Why don\'t you take CS303?'}, 143 | {'text': 'I hate computer science!'}, 144 | {'photo': 'some photo'}, 145 | {'video': 'some video'},] 146 | make_message_like(messages) 147 | 148 | for i in range(0,30): 149 | top_router.route(random.choice(messages)) 150 | print 151 | 152 | 153 | TOKEN = sys.argv[1] 154 | 155 | bot = telepot.Bot(TOKEN) 156 | bot._router.routing_table['chat'] = top_router.route 157 | 158 | bot.message_loop() 159 | print 'Send me some messages ...' 160 | 161 | while 1: 162 | time.sleep(10) 163 | -------------------------------------------------------------------------------- /test/test27_sticker.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from pprint import pprint 4 | import telepot 5 | from telepot.namedtuple import StickerSet 6 | 7 | TOKEN = sys.argv[1] 8 | USER_ID = long(sys.argv[2]) 9 | STICKER_SET = sys.argv[3] 10 | 11 | bot = telepot.Bot(TOKEN) 12 | 13 | f = bot.uploadStickerFile(USER_ID, open('gandhi.png', 'rb')) 14 | print 'Uploaded Gandhi' 15 | 16 | bot.addStickerToSet(USER_ID, STICKER_SET, f['file_id'], u'\U0001f60a') 17 | bot.addStickerToSet(USER_ID, STICKER_SET, open('lincoln.png', 'rb'), u'\U0001f60a') 18 | print 'Added Gandhi and Lincoln to set' 19 | 20 | s = bot.getStickerSet(STICKER_SET) 21 | pprint(s) 22 | 23 | ss = StickerSet(**s) 24 | 25 | for s in ss.stickers: 26 | bot.deleteStickerFromSet(s.file_id) 27 | print 'Deleted', s.file_id 28 | time.sleep(3) # throttle 29 | 30 | s = bot.getStickerSet(STICKER_SET) 31 | pprint(s) 32 | -------------------------------------------------------------------------------- /test/test27_text.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.text import apply_entities_as_markdown, apply_entities_as_html 5 | 6 | TOKEN = sys.argv[1] 7 | USER_ID = long(sys.argv[2]) 8 | 9 | bot = telepot.Bot(TOKEN) 10 | 11 | user_link = 'tg://user?id=' + str(USER_ID) 12 | 13 | markdowns = [ 14 | '*abc*de', 15 | 'ab_cd_e', 16 | '[abcde](http://www.yahoo.com/)', 17 | '[user]('+user_link+')', 18 | 'a`bcd`e', 19 | '''Below is a function: 20 | ```text 21 | def add(a, b): 22 | return a+b 23 | ``` 24 | Do you know what it does?''', 25 | 'a_bc_de*f*g`hijk`lmno', 26 | 'ab_cd_*efg*`h`[ijkl](http://www.yahoo.com/)', 27 | 'a*bcdefg*h[user]('+user_link+')', 28 | '''Markdown examples: 29 | *1.* \\*bold text\\* 30 | _2._ \\_italic text\\_ 31 | 3. \\[inline URL](http://www.example.com/) 32 | `4.` \\`inline fixed-width code\\`''', 33 | ] 34 | 35 | print 'Testing Markdown ...' 36 | for s in markdowns: 37 | msg = bot.sendMessage(USER_ID, s, parse_mode='Markdown') 38 | 39 | u = apply_entities_as_markdown(msg['text'], msg['entities']) 40 | 41 | if s == u: 42 | print 'Identical' 43 | else: 44 | print 'Different:' 45 | print 'Original ->', s 46 | print 'Applied ->', u 47 | 48 | time.sleep(2) 49 | 50 | 51 | htmls = [ 52 | 'a<b>bcd</b>e', 53 | '<i>ab</i>cde', 54 | 'ab<a href="http://www.yahoo.com/">cde</a>', 55 | 'ab<a href="'+user_link+'">user</a>', 56 | 'a<code>bcd</code>e', 57 | '''Below is a function: 58 | <pre> 59 | def add(a, b): 60 | return a+b 61 | </pre> 62 | Do you know what it does?''', 63 | 'a<i>bc</i>de<b>f</b>g<code>hijk</code>lmno', 64 | 'ab<i>cd</i><b>efg</b><code>h</code><a href="http://www.yahoo.com/">ijkl</a>', 65 | 'a<b>bcdefg</b>h<a href="'+user_link+'">user</a>', 66 | '''HTML examples: 67 | <b>1.</b> <b>bold</b> 68 | <i>2.</i> <i>italic</i> 69 | 3. <a href="http://www.example.com/">inline URL</a> 70 | <code>4.</code> <code>inline fixed-width code</code>''', 71 | ] 72 | 73 | print 'Testing HTML ...' 74 | for s in htmls: 75 | msg = bot.sendMessage(USER_ID, s, parse_mode='HTML') 76 | 77 | u = apply_entities_as_html(msg['text'], msg['entities']) 78 | 79 | if s == u: 80 | print 'Identical' 81 | else: 82 | print 'Different:' 83 | print 'Original ->', s 84 | print 'Applied ->', u 85 | 86 | time.sleep(2) 87 | -------------------------------------------------------------------------------- /test/test27_updates.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import time 4 | import threading 5 | import pprint 6 | import sys 7 | import traceback 8 | import telepot 9 | import telepot.namedtuple 10 | 11 | """ 12 | This script tests: 13 | - receiving all types of messages, by asking user to produce each 14 | 15 | Run it by: 16 | $ python2.7 test.py <token> <user_id> 17 | 18 | It will assume the bot identified by <token>, and only communicate with the user identified by <user_id>. 19 | 20 | If you don't know your user id, run: 21 | $ python test.py <token> 0 22 | 23 | And send it a message anyway. It will print out your user id as an unauthorized user. 24 | Ctrl-C to kill it, then run the proper command again. 25 | """ 26 | 27 | def equivalent(data, nt): 28 | if type(data) is dict: 29 | keys = data.keys() 30 | 31 | # number of dictionary keys == number of non-None values in namedtuple? 32 | if len(keys) != len([f for f in nt._fields if getattr(nt, f) is not None]): 33 | return False 34 | 35 | # map `from` to `from_` 36 | fields = list(map(lambda k: k+'_' if k in ['from'] else k, keys)) 37 | 38 | return all(map(equivalent, [data[k] for k in keys], [getattr(nt, f) for f in fields])) 39 | elif type(data) is list: 40 | return all(map(equivalent, data, nt)) 41 | else: 42 | return data==nt 43 | 44 | def examine(result, type): 45 | try: 46 | print 'Examining %s ......' % type 47 | 48 | nt = type(**result) 49 | assert equivalent(result, nt), 'Not equivalent:::::::::::::::\n%s\n::::::::::::::::\n%s' % (result, nt) 50 | 51 | pprint.pprint(result) 52 | pprint.pprint(nt) 53 | print 54 | except AssertionError: 55 | traceback.print_exc() 56 | answer = raw_input('Do you want to continue? [y] ') 57 | if answer != 'y': 58 | exit(1) 59 | 60 | expected_content_type = None 61 | content_type_iterator = iter([ 62 | 'text', 'voice', 'sticker', 'photo', 'audio' ,'document', 'video', 'contact', 'location', 63 | 'new_chat_member', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo', 'left_chat_member' 64 | ]) 65 | 66 | def see_every_content_types(msg): 67 | global expected_content_type, content_type_iterator 68 | 69 | flavor = telepot.flavor(msg) 70 | 71 | if flavor == 'chat': 72 | content_type, chat_type, chat_id = telepot.glance(msg) 73 | from_id = msg['from']['id'] 74 | 75 | if chat_id != USER_ID and from_id != USER_ID: 76 | print 'Unauthorized user:', chat_id, from_id 77 | return 78 | 79 | examine(msg, telepot.namedtuple.Message) 80 | try: 81 | if content_type == expected_content_type: 82 | expected_content_type = content_type_iterator.next() 83 | bot.sendMessage(chat_id, 'Please give me a %s.' % expected_content_type) 84 | else: 85 | bot.sendMessage(chat_id, 'It is not a %s. Please give me a %s, please.' % (expected_content_type, expected_content_type)) 86 | except StopIteration: 87 | # reply to sender because I am kicked from group already 88 | bot.sendMessage(from_id, 'Thank you. I am done.') 89 | 90 | else: 91 | raise telepot.BadFlavor(msg) 92 | 93 | 94 | TOKEN = sys.argv[1] 95 | USER_ID = long(sys.argv[2]) 96 | 97 | bot = telepot.Bot(TOKEN) 98 | 99 | expected_content_type = content_type_iterator.next() 100 | bot.sendMessage(USER_ID, 'Please give me a %s.' % expected_content_type) 101 | 102 | bot.message_loop(see_every_content_types, run_forever=True) 103 | -------------------------------------------------------------------------------- /test/test3_admin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | import telepot.namedtuple 5 | from telepot.routing import by_content_type, make_content_type_routing_table 6 | from telepot.exception import NotEnoughRightsError 7 | 8 | class AdminBot(telepot.Bot): 9 | def on_chat_message(self, msg): 10 | content_type, chat_type, chat_id = telepot.glance(msg) 11 | 12 | if 'edit_date' not in msg: 13 | self.sendMessage(chat_id, 'Edit the message, please.') 14 | else: 15 | self.sendMessage(chat_id, 'Add me to a group, please.') 16 | 17 | # Make a router to route `new_chat_member` and `left_chat_member` 18 | r = telepot.helper.Router(by_content_type(), make_content_type_routing_table(self)) 19 | 20 | # Replace current handler with that router 21 | self._router.routing_table['chat'] = r.route 22 | 23 | def on_new_chat_member(self, msg, new_chat_member): 24 | print('New chat member:', new_chat_member) 25 | content_type, chat_type, chat_id = telepot.glance(msg) 26 | 27 | r = self.getChat(chat_id) 28 | print(r) 29 | 30 | r = self.getChatAdministrators(chat_id) 31 | print(r) 32 | print(telepot.namedtuple.ChatMemberArray(r)) 33 | 34 | r = self.getChatMembersCount(chat_id) 35 | print(r) 36 | 37 | while 1: 38 | try: 39 | self.setChatTitle(chat_id, 'AdminBot Title') 40 | print('Set title successfully.') 41 | break 42 | except NotEnoughRightsError: 43 | print('No right to set title. Try again in 10 seconds ...') 44 | time.sleep(10) 45 | 46 | while 1: 47 | try: 48 | self.setChatPhoto(chat_id, open('gandhi.png', 'rb')) 49 | print('Set photo successfully.') 50 | time.sleep(2) # let tester see photo briefly 51 | break 52 | except NotEnoughRightsError: 53 | print('No right to set photo. Try again in 10 seconds ...') 54 | time.sleep(10) 55 | 56 | while 1: 57 | try: 58 | self.deleteChatPhoto(chat_id) 59 | print('Delete photo successfully.') 60 | break 61 | except NotEnoughRightsError: 62 | print('No right to delete photo. Try again in 10 seconds ...') 63 | time.sleep(10) 64 | 65 | print('I am done. Remove me from the group.') 66 | 67 | def on_left_chat_member(self, msg, left_chat_member): 68 | print('I see that I have left.') 69 | 70 | 71 | TOKEN = sys.argv[1] 72 | 73 | bot = AdminBot(TOKEN) 74 | bot.message_loop() 75 | print('Send me a text message ...') 76 | 77 | while 1: 78 | time.sleep(1) 79 | -------------------------------------------------------------------------------- /test/test3_inline.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import time 4 | import threading 5 | import pprint 6 | import sys 7 | import traceback 8 | import random 9 | import telepot 10 | from telepot.namedtuple import ( 11 | InlineQuery, ChosenInlineResult, InputTextMessageContent, 12 | InlineQueryResultArticle, InlineQueryResultPhoto, InlineQueryResultGame) 13 | 14 | def equivalent(data, nt): 15 | if type(data) is dict: 16 | keys = data.keys() 17 | 18 | # number of dictionary keys == number of non-None values in namedtuple? 19 | if len(keys) != len([f for f in nt._fields if getattr(nt, f) is not None]): 20 | return False 21 | 22 | # map `from` to `from_` 23 | fields = list(map(lambda k: k+'_' if k in ['from'] else k, keys)) 24 | 25 | return all(map(equivalent, [data[k] for k in keys], [getattr(nt, f) for f in fields])) 26 | elif type(data) is list: 27 | return all(map(equivalent, data, nt)) 28 | else: 29 | return data==nt 30 | 31 | def examine(result, type): 32 | try: 33 | print('Examining %s ......' % type) 34 | 35 | nt = type(**result) 36 | assert equivalent(result, nt), 'Not equivalent:::::::::::::::\n%s\n::::::::::::::::\n%s' % (result, nt) 37 | 38 | pprint.pprint(result) 39 | pprint.pprint(nt) 40 | print() 41 | except AssertionError: 42 | traceback.print_exc() 43 | answer = raw_input('Do you want to continue? [y] ') 44 | if answer != 'y': 45 | exit(1) 46 | 47 | def on_inline_query(msg): 48 | def compute(): 49 | articles = [InlineQueryResultArticle( 50 | id='abc', title='HK', input_message_content=InputTextMessageContent(message_text='Hong Kong'), url='https://www.google.com', hide_url=True), 51 | {'type': 'article', 52 | 'id': 'def', 'title': 'SZ', 'input_message_content': {'message_text': 'Shenzhen'}, 'url': 'https://www.yahoo.com'}] 53 | 54 | photos = [InlineQueryResultPhoto( 55 | id='123', photo_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf', thumb_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf'), 56 | {'type': 'photo', 57 | 'id': '345', 'photo_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'thumb_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'caption': 'Caption', 'title': 'Title', 'input_message_content': {'message_text': 'Shenzhen'}}] 58 | 59 | games = [InlineQueryResultGame( 60 | id='abc', game_short_name='sunchaser')] 61 | 62 | results = random.choice([articles, photos, games]) 63 | return results 64 | 65 | query_id, from_id, query = telepot.glance(msg, flavor='inline_query') 66 | 67 | if from_id != USER_ID: 68 | print('Unauthorized user:', from_id) 69 | return 70 | 71 | examine(msg, InlineQuery) 72 | answerer.answer(msg, compute) 73 | 74 | 75 | def on_chosen_inline_result(msg): 76 | result_id, from_id, query = telepot.glance(msg, flavor='chosen_inline_result') 77 | 78 | if from_id != USER_ID: 79 | print('Unauthorized user:', from_id) 80 | return 81 | 82 | examine(msg, ChosenInlineResult) 83 | 84 | print('Chosen inline query:') 85 | pprint.pprint(msg) 86 | 87 | 88 | def compute(inline_query): 89 | articles = [InlineQueryResultArticle( 90 | id='abc', title='HK', message_text='Hong Kong', url='https://www.google.com', hide_url=True), 91 | {'type': 'article', 92 | 'id': 'def', 'title': 'SZ', 'message_text': 'Shenzhen', 'url': 'https://www.yahoo.com'}] 93 | 94 | photos = [InlineQueryResultPhoto( 95 | id='123', photo_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf', thumb_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf'), 96 | {'type': 'photo', 97 | 'id': '345', 'photo_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'thumb_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'caption': 'Caption', 'title': 'Title', 'message_text': 'Message Text'}] 98 | 99 | results = random.choice([articles, photos]) 100 | return results 101 | 102 | 103 | TOKEN = sys.argv[1] 104 | USER_ID = int(sys.argv[2]) 105 | 106 | bot = telepot.Bot(TOKEN) 107 | answerer = telepot.helper.Answerer(bot) 108 | 109 | bot.sendMessage(USER_ID, 'Please give me an inline query.') 110 | 111 | bot.message_loop({'inline_query': on_inline_query, 112 | 'chosen_inline_result': on_chosen_inline_result}, run_forever=True) 113 | -------------------------------------------------------------------------------- /test/test3_pay.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from pprint import pprint 4 | import telepot 5 | from telepot.namedtuple import ( 6 | LabeledPrice, Invoice, PreCheckoutQuery, ShippingQuery, ShippingOption, 7 | SuccessfulPayment) 8 | from telepot.loop import MessageLoop 9 | 10 | """ 11 | This script tests the payment process: 12 | 1. Send an invoice 13 | 2. Receive a shipping query, respond with answerShippingQuery() 14 | 3. Receive a pre-checkout query, respond with answerPreCheckoutQuery() 15 | 4. Receive a successful payment 16 | 17 | Run it by: 18 | $ python3.5 script.py <bot-token> <payment-provider-token> 19 | """ 20 | 21 | def on_chat_message(msg): 22 | content_type, chat_type, chat_id = telepot.glance(msg) 23 | print(content_type, chat_type, chat_id) 24 | 25 | if content_type != 'successful_payment': 26 | sent = bot.sendInvoice( 27 | chat_id, "Nick's Hand Cream", "Keep a man's hand like a woman's", 28 | payload='a-string-identifying-related-payment-messages-tuvwxyz', 29 | provider_token=PAYMENT_PROVIDER_TOKEN, 30 | start_parameter='abc', 31 | currency='HKD', prices=[ 32 | LabeledPrice(label='One Case', amount=987), 33 | LabeledPrice(label='Package', amount=12)], 34 | need_shipping_address=True, is_flexible=True) # required for shipping query 35 | # 'Pay' button appears automatically 36 | 37 | pprint(sent) 38 | print(Invoice(**sent['invoice'])) 39 | 40 | else: 41 | print('Successful payment RECEIVED!!!') 42 | pprint(msg) 43 | print(SuccessfulPayment(**msg['successful_payment'])) 44 | 45 | def on_shipping_query(msg): 46 | query_id, from_id, invoice_payload = telepot.glance(msg, flavor='shipping_query') 47 | 48 | print('Shipping query:') 49 | print(query_id, from_id, invoice_payload) 50 | pprint(msg) 51 | print(ShippingQuery(**msg)) 52 | 53 | bot.answerShippingQuery( 54 | query_id, True, 55 | shipping_options=[ 56 | ShippingOption(id='fedex', title='FedEx', prices=[ 57 | LabeledPrice(label='Local', amount=345), 58 | LabeledPrice(label='International', amount=2345)]), 59 | ShippingOption(id='dhl', title='DHL', prices=[ 60 | LabeledPrice(label='Local', amount=342), 61 | LabeledPrice(label='International', amount=1234)])]) 62 | 63 | def on_pre_checkout_query(msg): 64 | query_id, from_id, invoice_payload, currency, total_amount = telepot.glance(msg, flavor='pre_checkout_query', long=True) 65 | 66 | print('Pre-Checkout query:') 67 | print(query_id, from_id, invoice_payload, currency, total_amount) 68 | pprint(msg) 69 | print(PreCheckoutQuery(**msg)) 70 | 71 | bot.answerPreCheckoutQuery(query_id, True) 72 | 73 | TOKEN = sys.argv[1] 74 | PAYMENT_PROVIDER_TOKEN = sys.argv[2] 75 | 76 | bot = telepot.Bot(TOKEN) 77 | MessageLoop(bot, {'chat': on_chat_message, 78 | 'shipping_query': on_shipping_query, 79 | 'pre_checkout_query': on_pre_checkout_query}).run_as_thread() 80 | 81 | while 1: 82 | time.sleep(10) 83 | -------------------------------------------------------------------------------- /test/test3_queue.py: -------------------------------------------------------------------------------- 1 | import time 2 | import queue 3 | import telepot 4 | from telepot.loop import OrderedWebhook 5 | 6 | def u(update_id): 7 | return { 'update_id': update_id, 'message': update_id } 8 | 9 | sequence = [ 10 | u(1), # initialize 11 | u(2), # no buffering 12 | 13 | u(4), # 1-gap 14 | u(3), # clear 2 15 | 16 | u(7), # 2-gap 17 | u(5), # return, leave 1-gap 18 | u(6), # clear 2 19 | 20 | u(10), # 2-gap 21 | u(9), # 1-gap 22 | u(8), # clear 3 23 | 24 | u(15), 25 | u(12), 26 | u(13), 27 | u(11), 28 | u(14), 29 | 30 | u(17), 31 | u(18), 32 | u(21), 33 | u(20), 34 | u(19), 35 | u(16), 36 | 37 | u(22), # no buffering 38 | 39 | u(24), 40 | 9, # skip id=23 41 | 42 | u(23), # discard 43 | 44 | u(26), 45 | u(27), 46 | 9, # skip id=25 47 | 48 | u(25), # discard 49 | 50 | u(30), 51 | u(29), 52 | 5, 53 | u(32), 54 | u(33), 55 | 2, # clear 29,30, skip 28 56 | u(31), # clear 31,32,33 57 | 58 | u(39), 59 | u(36), 60 | 2, 61 | u(37), 62 | 7, # clear 36,37,39 63 | 64 | u(28), # discard 65 | u(38), # discard 66 | 67 | u(40), # return 68 | ] 69 | 70 | def handle(msg): 71 | print(msg) 72 | 73 | bot = telepot.Bot('abc') 74 | webhook = OrderedWebhook(bot, handle) 75 | 76 | webhook.run_as_thread(maxhold=8) 77 | 78 | for update in sequence: 79 | if type(update) is dict: 80 | webhook.feed(update) 81 | time.sleep(1) 82 | else: 83 | time.sleep(update) 84 | -------------------------------------------------------------------------------- /test/test3_routing.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import random 4 | import telepot.helper 5 | from telepot.routing import (by_content_type, make_content_type_routing_table, 6 | lower_key, by_chat_command, make_routing_table, 7 | by_regex) 8 | 9 | def random_key(msg): 10 | return random.choice([ 11 | 0, 12 | (1,), 13 | (2, ('a1',)), 14 | (3, ('a1', 'a2'), {'b1': 'b'}), 15 | (4, (), {'kw4': 4444, 'kw5': 'xyz'}), 16 | ((None,), ()), 17 | ]) 18 | 19 | def zero(msg): 20 | print('Zero') 21 | 22 | def one(msg): 23 | print('One') 24 | 25 | def two(msg, a1): 26 | print('Two', a1) 27 | 28 | def three(msg, a1, a2, b1): 29 | print('Three', a1, a2, b1) 30 | 31 | def none_tuple(msg): 32 | print('None tuple') 33 | 34 | def none_of_above(msg, *args, **kwargs): 35 | print('None of above', msg, args, kwargs) 36 | 37 | top_router = telepot.helper.Router(random_key, {0: zero, 38 | 1: one, 39 | 2: two, 40 | 3: three, 41 | (None,): none_tuple, 42 | None: none_of_above}) 43 | 44 | for i in range(0,20): 45 | top_router.route({}) 46 | print() 47 | 48 | 49 | class ContentTypeHandler(object): 50 | def on_text(self, msg, text): 51 | print('Text', msg, text) 52 | 53 | def on_photo(self, msg, photo): 54 | print('Photo', msg, photo) 55 | 56 | def make_message_like(mm): 57 | for d in mm: 58 | d.update({'chat': {'type': 'private', 'id': 1000}}) 59 | 60 | top_router.key_function = by_content_type() 61 | top_router.routing_table = make_content_type_routing_table(ContentTypeHandler()) 62 | del top_router.routing_table['video'] # let video fall to default handler 63 | top_router.routing_table[None] = none_of_above 64 | 65 | messages = [{'text': 'abc'}, 66 | {'photo': 'some photo'}, 67 | {'video': 'some video'},] 68 | make_message_like(messages) 69 | 70 | for i in range(0,10): 71 | top_router.route(random.choice(messages)) 72 | print() 73 | 74 | 75 | class CommandHandler(object): 76 | def on_start(self, msg): 77 | print('Command: start', msg) 78 | 79 | def on_settings(self, msg): 80 | print('Command: settings', msg) 81 | 82 | def on_invalid_text(self, msg): 83 | print('Invalid text', msg) 84 | 85 | def on_invalid_command(self, msg): 86 | print('Invalid command', msg) 87 | 88 | command_handler = CommandHandler() 89 | command_router = telepot.helper.Router(lower_key(by_chat_command()), 90 | make_routing_table(command_handler, [ 91 | 'start', 92 | 'settings', 93 | ((None,), command_handler.on_invalid_text), 94 | (None, command_handler.on_invalid_command), 95 | ])) 96 | 97 | top_router.routing_table['text'] = command_router.route 98 | 99 | messages = [{'text': '/start'}, 100 | {'text': '/SETTINGS'}, 101 | {'text': '/bad'}, 102 | {'text': 'plain text'}, 103 | {'photo': 'some photo'}, 104 | {'video': 'some video'},] 105 | make_message_like(messages) 106 | 107 | for i in range(0,20): 108 | top_router.route(random.choice(messages)) 109 | print() 110 | 111 | 112 | class RegexHandler(object): 113 | def on_CS101(self, msg, match): 114 | print('Someone mentioned CS101 !!!', msg, match.groups()) 115 | 116 | def on_CS202(self, msg, match): 117 | print('Someone mentioned CS202 !!!', msg, match.groups()) 118 | 119 | def no_cs_courses_mentioned(self, msg): 120 | print('No CS courses mentioned ...', msg) 121 | 122 | def course_not_exist(self, msg, match): 123 | print('%s does not exist' % match.group(1), msg) 124 | 125 | regex_handler = RegexHandler() 126 | regex_router = telepot.helper.Router(by_regex(lambda msg: msg['text'], '(CS[0-9]{3})'), 127 | make_routing_table(regex_handler, [ 128 | 'CS101', 129 | 'CS202', 130 | ((None,), regex_handler.no_cs_courses_mentioned), 131 | (None, regex_handler.course_not_exist), 132 | ])) 133 | 134 | command_router.routing_table[(None,)] = regex_router.route 135 | 136 | messages = [{'text': '/start'}, 137 | {'text': '/SETTINGS'}, 138 | {'text': '/bad'}, 139 | {'text': 'plain text'}, 140 | {'text': 'I want to take CS101.'}, 141 | {'text': 'I\'d rather take CS202.'}, 142 | {'text': 'Why don\'t you take CS303?'}, 143 | {'text': 'I hate computer science!'}, 144 | {'photo': 'some photo'}, 145 | {'video': 'some video'},] 146 | make_message_like(messages) 147 | 148 | for i in range(0,30): 149 | top_router.route(random.choice(messages)) 150 | print() 151 | 152 | 153 | TOKEN = sys.argv[1] 154 | 155 | bot = telepot.Bot(TOKEN) 156 | bot._router.routing_table['chat'] = top_router.route 157 | 158 | bot.message_loop() 159 | print('Send me some messages ...') 160 | 161 | while 1: 162 | time.sleep(10) 163 | -------------------------------------------------------------------------------- /test/test3_sticker.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from pprint import pprint 4 | import telepot 5 | from telepot.namedtuple import StickerSet 6 | 7 | TOKEN = sys.argv[1] 8 | USER_ID = int(sys.argv[2]) 9 | STICKER_SET = sys.argv[3] 10 | 11 | bot = telepot.Bot(TOKEN) 12 | 13 | f = bot.uploadStickerFile(USER_ID, open('gandhi.png', 'rb')) 14 | print('Uploaded Gandhi') 15 | 16 | bot.addStickerToSet(USER_ID, STICKER_SET, f['file_id'], '\U0001f60a') 17 | bot.addStickerToSet(USER_ID, STICKER_SET, open('lincoln.png', 'rb'), '\U0001f60a') 18 | print('Added Gandhi and Lincoln to set') 19 | 20 | s = bot.getStickerSet(STICKER_SET) 21 | pprint(s) 22 | 23 | ss = StickerSet(**s) 24 | 25 | for s in ss.stickers: 26 | bot.deleteStickerFromSet(s.file_id) 27 | print('Deleted', s.file_id) 28 | time.sleep(3) # throttle 29 | 30 | s = bot.getStickerSet(STICKER_SET) 31 | pprint(s) 32 | -------------------------------------------------------------------------------- /test/test3_text.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import telepot 4 | from telepot.text import apply_entities_as_markdown, apply_entities_as_html 5 | 6 | TOKEN = sys.argv[1] 7 | USER_ID = int(sys.argv[2]) 8 | 9 | bot = telepot.Bot(TOKEN) 10 | 11 | user_link = 'tg://user?id=' + str(USER_ID) 12 | 13 | markdowns = [ 14 | '*abc*de', 15 | 'ab_cd_e', 16 | '[abcde](http://www.yahoo.com/)', 17 | '[user]('+user_link+')', 18 | 'a`bcd`e', 19 | '''Below is a function: 20 | ```text 21 | def add(a, b): 22 | return a+b 23 | ``` 24 | Do you know what it does?''', 25 | 'a_bc_de*f*g`hijk`lmno', 26 | 'ab_cd_*efg*`h`[ijkl](http://www.yahoo.com/)', 27 | 'a*bcdefg*h[user]('+user_link+')', 28 | '''Markdown examples: 29 | *1.* \\*bold text\\* 30 | _2._ \\_italic text\\_ 31 | 3. \\[inline URL](http://www.example.com/) 32 | `4.` \\`inline fixed-width code\\`''', 33 | ] 34 | 35 | print('Testing Markdown ...') 36 | for s in markdowns: 37 | msg = bot.sendMessage(USER_ID, s, parse_mode='Markdown') 38 | 39 | u = apply_entities_as_markdown(msg['text'], msg['entities']) 40 | 41 | if s == u: 42 | print('Identical') 43 | else: 44 | print('Different:') 45 | print('Original ->', s) 46 | print('Applied ->', u) 47 | 48 | time.sleep(2) 49 | 50 | 51 | htmls = [ 52 | 'a<b>bcd</b>e', 53 | '<i>ab</i>cde', 54 | 'ab<a href="http://www.yahoo.com/">cde</a>', 55 | 'ab<a href="'+user_link+'">user</a>', 56 | 'a<code>bcd</code>e', 57 | '''Below is a function: 58 | <pre> 59 | def add(a, b): 60 | return a+b 61 | </pre> 62 | Do you know what it does?''', 63 | 'a<i>bc</i>de<b>f</b>g<code>hijk</code>lmno', 64 | 'ab<i>cd</i><b>efg</b><code>h</code><a href="http://www.yahoo.com/">ijkl</a>', 65 | 'a<b>bcdefg</b>h<a href="'+user_link+'">user</a>', 66 | '''HTML examples: 67 | <b>1.</b> <b>bold</b> 68 | <i>2.</i> <i>italic</i> 69 | 3. <a href="http://www.example.com/">inline URL</a> 70 | <code>4.</code> <code>inline fixed-width code</code>''', 71 | ] 72 | 73 | print('Testing HTML ...') 74 | for s in htmls: 75 | msg = bot.sendMessage(USER_ID, s, parse_mode='HTML') 76 | 77 | u = apply_entities_as_html(msg['text'], msg['entities']) 78 | 79 | if s == u: 80 | print('Identical') 81 | else: 82 | print('Different:') 83 | print('Original ->', s) 84 | print('Applied ->', u) 85 | 86 | time.sleep(2) 87 | -------------------------------------------------------------------------------- /test/test3_updates.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import time 4 | import threading 5 | import pprint 6 | import sys 7 | import traceback 8 | import telepot 9 | import telepot.namedtuple 10 | 11 | """ 12 | This script tests: 13 | - receiving all types of messages, by asking user to produce each 14 | 15 | Run it by: 16 | $ python3.X test3.py <token> <user_id> 17 | 18 | It will assume the bot identified by <token>, and only communicate with the user identified by <user_id>. 19 | 20 | If you don't know your user id, run: 21 | $ python test.py <token> 0 22 | 23 | And send it a message anyway. It will print out your user id as an unauthorized user. 24 | Ctrl-C to kill it, then run the proper command again. 25 | """ 26 | 27 | def equivalent(data, nt): 28 | if type(data) is dict: 29 | keys = list(data.keys()) 30 | 31 | # number of dictionary keys == number of non-None values in namedtuple? 32 | if len(keys) != len([f for f in nt._fields if getattr(nt, f) is not None]): 33 | return False 34 | 35 | # map `from` to `from_` 36 | fields = list([k+'_' if k in ['from'] else k for k in keys]) 37 | 38 | return all(map(equivalent, [data[k] for k in keys], [getattr(nt, f) for f in fields])) 39 | elif type(data) is list: 40 | return all(map(equivalent, data, nt)) 41 | else: 42 | return data==nt 43 | 44 | def examine(result, type): 45 | try: 46 | print('Examining %s ......' % type) 47 | 48 | nt = type(**result) 49 | assert equivalent(result, nt), 'Not equivalent:::::::::::::::\n%s\n::::::::::::::::\n%s' % (result, nt) 50 | 51 | pprint.pprint(result) 52 | pprint.pprint(nt) 53 | print() 54 | except AssertionError: 55 | traceback.print_exc() 56 | answer = input('Do you want to continue? [y] ') 57 | if answer != 'y': 58 | exit(1) 59 | 60 | expected_content_type = None 61 | content_type_iterator = iter([ 62 | 'text', 'voice', 'sticker', 'photo', 'audio' ,'document', 'video', 'contact', 'location', 63 | 'new_chat_member', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo', 'left_chat_member' 64 | ]) 65 | 66 | def see_every_content_types(msg): 67 | global expected_content_type, content_type_iterator 68 | 69 | content_type, chat_type, chat_id = telepot.glance(msg) 70 | from_id = msg['from']['id'] 71 | 72 | if chat_id != USER_ID and from_id != USER_ID: 73 | print('Unauthorized user:', chat_id, from_id) 74 | return 75 | 76 | examine(msg, telepot.namedtuple.Message) 77 | try: 78 | if content_type == expected_content_type: 79 | expected_content_type = next(content_type_iterator) 80 | bot.sendMessage(chat_id, 'Please give me a %s.' % expected_content_type) 81 | else: 82 | bot.sendMessage(chat_id, 'It is not a %s. Please give me a %s, please.' % (expected_content_type, expected_content_type)) 83 | except StopIteration: 84 | # reply to sender because I am kicked from group already 85 | bot.sendMessage(from_id, 'Thank you. I am done.') 86 | 87 | TOKEN = sys.argv[1] 88 | USER_ID = int(sys.argv[2]) 89 | 90 | # telepot.api.set_proxy('http://192.168.0.103:3128') 91 | 92 | bot = telepot.Bot(TOKEN) 93 | 94 | expected_content_type = content_type_iterator.__next__() 95 | bot.sendMessage(USER_ID, 'Please give me a %s.' % expected_content_type) 96 | 97 | bot.message_loop(see_every_content_types, run_forever=True) 98 | -------------------------------------------------------------------------------- /test/test3a_admin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import telepot 4 | import telepot.namedtuple 5 | import telepot.aio 6 | from telepot.aio.routing import by_content_type, make_content_type_routing_table 7 | from telepot.exception import NotEnoughRightsError 8 | 9 | class AdminBot(telepot.aio.Bot): 10 | async def on_chat_message(self, msg): 11 | content_type, chat_type, chat_id = telepot.glance(msg) 12 | 13 | if 'edit_date' not in msg: 14 | await self.sendMessage(chat_id, 'Edit the message, please.') 15 | else: 16 | await self.sendMessage(chat_id, 'Add me to a group, please.') 17 | 18 | # Make a router to route `new_chat_member` and `left_chat_member` 19 | r = telepot.aio.helper.Router(by_content_type(), make_content_type_routing_table(self)) 20 | 21 | # Replace current handler with that router 22 | self._router.routing_table['chat'] = r.route 23 | 24 | async def on_new_chat_member(self, msg, new_chat_member): 25 | print('New chat member:', new_chat_member) 26 | content_type, chat_type, chat_id = telepot.glance(msg) 27 | 28 | r = await self.getChat(chat_id) 29 | print(r) 30 | 31 | r = await self.getChatAdministrators(chat_id) 32 | print(r) 33 | print(telepot.namedtuple.ChatMemberArray(r)) 34 | 35 | r = await self.getChatMembersCount(chat_id) 36 | print(r) 37 | 38 | while 1: 39 | try: 40 | await self.setChatTitle(chat_id, 'AdminBot Title') 41 | print('Set title successfully.') 42 | break 43 | except NotEnoughRightsError: 44 | print('No right to set title. Try again in 10 seconds ...') 45 | await asyncio.sleep(10) 46 | 47 | while 1: 48 | try: 49 | await self.setChatPhoto(chat_id, open('gandhi.png', 'rb')) 50 | print('Set photo successfully.') 51 | await asyncio.sleep(2) # let tester see photo briefly 52 | break 53 | except NotEnoughRightsError: 54 | print('No right to set photo. Try again in 10 seconds ...') 55 | await asyncio.sleep(10) 56 | 57 | while 1: 58 | try: 59 | await self.deleteChatPhoto(chat_id) 60 | print('Delete photo successfully.') 61 | break 62 | except NotEnoughRightsError: 63 | print('No right to delete photo. Try again in 10 seconds ...') 64 | await asyncio.sleep(10) 65 | 66 | print('I am done. Remove me from the group.') 67 | 68 | async def on_left_chat_member(self, msg, left_chat_member): 69 | print('I see that I have left.') 70 | 71 | 72 | TOKEN = sys.argv[1] 73 | 74 | bot = AdminBot(TOKEN) 75 | loop = asyncio.get_event_loop() 76 | #loop.set_debug(True) 77 | 78 | loop.create_task(bot.message_loop()) 79 | print('Send me a text message ...') 80 | 81 | loop.run_forever() 82 | -------------------------------------------------------------------------------- /test/test3a_inline.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | 3 | import asyncio 4 | import time 5 | import threading 6 | import pprint 7 | import sys 8 | import traceback 9 | import random 10 | import telepot 11 | import telepot.aio 12 | from telepot.namedtuple import ( 13 | InlineQuery, ChosenInlineResult, InputTextMessageContent, 14 | InlineQueryResultArticle, InlineQueryResultPhoto, InlineQueryResultGame) 15 | 16 | def equivalent(data, nt): 17 | if type(data) is dict: 18 | keys = list(data.keys()) 19 | 20 | # number of dictionary keys == number of non-None values in namedtuple? 21 | if len(keys) != len([f for f in nt._fields if getattr(nt, f) is not None]): 22 | return False 23 | 24 | # map `from` to `from_` 25 | fields = list([k+'_' if k in ['from'] else k for k in keys]) 26 | 27 | return all(map(equivalent, [data[k] for k in keys], [getattr(nt, f) for f in fields])) 28 | elif type(data) is list: 29 | return all(map(equivalent, data, nt)) 30 | else: 31 | return data==nt 32 | 33 | def examine(result, type): 34 | try: 35 | print('Examining %s ......' % type) 36 | 37 | nt = type(**result) 38 | assert equivalent(result, nt), 'Not equivalent:::::::::::::::\n%s\n::::::::::::::::\n%s' % (result, nt) 39 | 40 | pprint.pprint(result) 41 | pprint.pprint(nt) 42 | print() 43 | except AssertionError: 44 | traceback.print_exc() 45 | print('Do you want to continue? [y]', end=' ') 46 | answer = input() 47 | if answer != 'y': 48 | exit(1) 49 | 50 | 51 | def on_inline_query(msg): 52 | def compute(): 53 | articles = [InlineQueryResultArticle( 54 | id='abc', title='HK', input_message_content=InputTextMessageContent(message_text='Hong Kong'), url='https://www.google.com', hide_url=True), 55 | {'type': 'article', 56 | 'id': 'def', 'title': 'SZ', 'input_message_content': {'message_text': 'Shenzhen'}, 'url': 'https://www.yahoo.com'}] 57 | 58 | photos = [InlineQueryResultPhoto( 59 | id='123', photo_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf', thumb_url='https://core.telegram.org/file/811140934/1/tbDSLHSaijc/fdcc7b6d5fb3354adf'), 60 | {'type': 'photo', 61 | 'id': '345', 'photo_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'thumb_url': 'https://core.telegram.org/file/811140184/1/5YJxx-rostA/ad3f74094485fb97bd', 'caption': 'Caption', 'title': 'Title', 'input_message_content': {'message_text': 'Shenzhen'}}] 62 | 63 | games = [InlineQueryResultGame( 64 | id='abc', game_short_name='sunchaser')] 65 | 66 | results = random.choice([articles, photos, games]) 67 | return results 68 | 69 | query_id, from_id, query = telepot.glance(msg, flavor='inline_query') 70 | 71 | if from_id != USER_ID: 72 | print('Unauthorized user:', from_id) 73 | return 74 | 75 | examine(msg, InlineQuery) 76 | answerer.answer(msg, compute) 77 | 78 | 79 | def on_chosen_inline_result(msg): 80 | result_id, from_id, query = telepot.glance(msg, flavor='chosen_inline_result') 81 | 82 | if from_id != USER_ID: 83 | print('Unauthorized user:', from_id) 84 | return 85 | 86 | examine(msg, ChosenInlineResult) 87 | 88 | print('Chosen inline query:') 89 | pprint.pprint(msg) 90 | 91 | 92 | TOKEN = sys.argv[1] 93 | USER_ID = int(sys.argv[2]) 94 | 95 | bot = telepot.aio.Bot(TOKEN) 96 | answerer = telepot.aio.helper.Answerer(bot) 97 | loop = asyncio.get_event_loop() 98 | 99 | print('Give me an inline query.') 100 | loop.create_task(bot.message_loop({'inline_query': on_inline_query, 101 | 'chosen_inline_result': on_chosen_inline_result})) 102 | loop.run_forever() 103 | -------------------------------------------------------------------------------- /test/test3a_pay.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from pprint import pprint 4 | import telepot 5 | import telepot.aio 6 | from telepot.namedtuple import ( 7 | LabeledPrice, Invoice, PreCheckoutQuery, ShippingQuery, ShippingOption, 8 | SuccessfulPayment) 9 | from telepot.aio.loop import MessageLoop 10 | 11 | """ 12 | This script tests the payment process: 13 | 1. Send an invoice 14 | 2. Receive a shipping query, respond with answerShippingQuery() 15 | 3. Receive a pre-checkout query, respond with answerPreCheckoutQuery() 16 | 4. Receive a successful payment 17 | 18 | Run it by: 19 | $ python3.5 script.py <bot-token> <payment-provider-token> 20 | """ 21 | 22 | async def on_chat_message(msg): 23 | content_type, chat_type, chat_id = telepot.glance(msg) 24 | print(content_type, chat_type, chat_id) 25 | 26 | if content_type != 'successful_payment': 27 | sent = await bot.sendInvoice( 28 | chat_id, "Nick's Hand Cream", "Keep a man's hand like a woman's", 29 | payload='a-string-identifying-related-payment-messages-tuvwxyz', 30 | provider_token=PAYMENT_PROVIDER_TOKEN, 31 | start_parameter='abc', 32 | currency='HKD', prices=[ 33 | LabeledPrice(label='One Case', amount=987), 34 | LabeledPrice(label='Package', amount=12)], 35 | need_shipping_address=True, is_flexible=True) # required for shipping query 36 | # 'Pay' button appears automatically 37 | 38 | pprint(sent) 39 | print(Invoice(**sent['invoice'])) 40 | 41 | else: 42 | print('Successful payment RECEIVED!!!') 43 | pprint(msg) 44 | print(SuccessfulPayment(**msg['successful_payment'])) 45 | 46 | async def on_shipping_query(msg): 47 | query_id, from_id, invoice_payload = telepot.glance(msg, flavor='shipping_query') 48 | 49 | print('Shipping query:') 50 | print(query_id, from_id, invoice_payload) 51 | pprint(msg) 52 | print(ShippingQuery(**msg)) 53 | 54 | await bot.answerShippingQuery( 55 | query_id, True, 56 | shipping_options=[ 57 | ShippingOption(id='fedex', title='FedEx', prices=[ 58 | LabeledPrice(label='Local', amount=345), 59 | LabeledPrice(label='International', amount=2345)]), 60 | ShippingOption(id='dhl', title='DHL', prices=[ 61 | LabeledPrice(label='Local', amount=342), 62 | LabeledPrice(label='International', amount=1234)])]) 63 | 64 | async def on_pre_checkout_query(msg): 65 | query_id, from_id, invoice_payload, currency, total_amount = telepot.glance(msg, flavor='pre_checkout_query', long=True) 66 | 67 | print('Pre-Checkout query:') 68 | print(query_id, from_id, invoice_payload, currency, total_amount) 69 | pprint(msg) 70 | print(PreCheckoutQuery(**msg)) 71 | 72 | await bot.answerPreCheckoutQuery(query_id, True) 73 | 74 | TOKEN = sys.argv[1] 75 | PAYMENT_PROVIDER_TOKEN = sys.argv[2] 76 | 77 | bot = telepot.aio.Bot(TOKEN) 78 | loop = asyncio.get_event_loop() 79 | 80 | loop.create_task( 81 | MessageLoop(bot, { 82 | 'chat': on_chat_message, 83 | 'shipping_query': on_shipping_query, 84 | 'pre_checkout_query': on_pre_checkout_query}).run_forever()) 85 | 86 | loop.run_forever() 87 | -------------------------------------------------------------------------------- /test/test3a_queue.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | import telepot.aio 4 | from telepot.aio.loop import OrderedWebhook 5 | 6 | def u(update_id): 7 | return { 'update_id': update_id, 'message': update_id } 8 | 9 | sequence = [ 10 | u(1), # initialize 11 | u(2), # no buffering 12 | 13 | u(4), # 1-gap 14 | u(3), # clear 2 15 | 16 | u(7), # 2-gap 17 | u(5), # return, leave 1-gap 18 | u(6), # clear 2 19 | 20 | u(10), # 2-gap 21 | u(9), # 1-gap 22 | u(8), # clear 3 23 | 24 | u(15), 25 | u(12), 26 | u(13), 27 | u(11), 28 | u(14), 29 | 30 | u(17), 31 | u(18), 32 | u(21), 33 | u(20), 34 | u(19), 35 | u(16), 36 | 37 | u(22), # no buffering 38 | 39 | u(24), 40 | 9, # skip id=23 41 | 42 | u(23), # discard 43 | 44 | u(26), 45 | u(27), 46 | 9, # skip id=25 47 | 48 | u(25), # discard 49 | 50 | u(30), 51 | u(29), 52 | 5, 53 | u(32), 54 | u(33), 55 | 2, # clear 29,30, skip 28 56 | u(31), # clear 31,32,33 57 | 58 | u(39), 59 | u(36), 60 | 2, 61 | u(37), 62 | 7, # clear 36,37,39 63 | 64 | u(28), # discard 65 | u(38), # discard 66 | 67 | u(40), # return 68 | ] 69 | 70 | async def feed(): 71 | for update in sequence: 72 | if type(update) is dict: 73 | webhook.feed(update) 74 | await asyncio.sleep(1) 75 | else: 76 | await asyncio.sleep(update) 77 | 78 | def handle(msg): 79 | print(msg) 80 | 81 | bot = telepot.aio.Bot('abc') 82 | webhook = OrderedWebhook(bot, handle) 83 | 84 | loop = asyncio.get_event_loop() 85 | loop.create_task(webhook.run_forever(maxhold=8)) 86 | loop.create_task(feed()) 87 | loop.run_forever() 88 | -------------------------------------------------------------------------------- /test/test3a_routing.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import random 4 | import telepot.aio 5 | from telepot.aio.routing import (by_content_type, make_content_type_routing_table, 6 | lower_key, by_chat_command, make_routing_table, 7 | by_regex) 8 | 9 | def random_key(msg): 10 | return random.choice([ 11 | 0, 12 | (1,), 13 | (2, ('a1',)), 14 | (3, ('a1', 'a2'), {'b1': 'b'}), 15 | (4, (), {'kw4': 4444, 'kw5': 'xyz'}), 16 | ((None,), ()), 17 | ]) 18 | 19 | def zero(msg): 20 | print('Zero') 21 | 22 | def one(msg): 23 | print('One') 24 | 25 | def two(msg, a1): 26 | print('Two', a1) 27 | 28 | def three(msg, a1, a2, b1): 29 | print('Three', a1, a2, b1) 30 | 31 | def none_tuple(msg): 32 | print('None tuple') 33 | 34 | def none_of_above(msg, *args, **kwargs): 35 | print('None of above', msg, args, kwargs) 36 | 37 | top_router = telepot.aio.helper.Router(random_key, {0: zero, 38 | 1: one, 39 | 2: two, 40 | 3: three, 41 | (None,): none_tuple, 42 | None: none_of_above}) 43 | 44 | async def fake0(): 45 | for i in range(0,20): 46 | await top_router.route({}) 47 | print() 48 | 49 | 50 | class ContentTypeHandler(object): 51 | def on_text(self, msg, text): 52 | print('Text', msg, text) 53 | 54 | def on_photo(self, msg, photo): 55 | print('Photo', msg, photo) 56 | 57 | def make_message_like(mm): 58 | for d in mm: 59 | d.update({'chat': {'type': 'private', 'id': 1000}}) 60 | 61 | async def fake1(): 62 | top_router.key_function = by_content_type() 63 | top_router.routing_table = make_content_type_routing_table(ContentTypeHandler()) 64 | del top_router.routing_table['video'] # let video fall to default handler 65 | top_router.routing_table[None] = none_of_above 66 | 67 | messages = [{'text': 'abc'}, 68 | {'photo': 'some photo'}, 69 | {'video': 'some video'},] 70 | make_message_like(messages) 71 | 72 | for i in range(0,10): 73 | await top_router.route(random.choice(messages)) 74 | print() 75 | 76 | 77 | class CommandHandler(object): 78 | def on_start(self, msg): 79 | print('Command: start', msg) 80 | 81 | def on_settings(self, msg): 82 | print('Command: settings', msg) 83 | 84 | def on_invalid_text(self, msg): 85 | print('Invalid text', msg) 86 | 87 | def on_invalid_command(self, msg): 88 | print('Invalid command', msg) 89 | 90 | command_handler = CommandHandler() 91 | command_router = telepot.aio.helper.Router(lower_key(by_chat_command()), 92 | make_routing_table(command_handler, [ 93 | 'start', 94 | 'settings', 95 | ((None,), command_handler.on_invalid_text), 96 | (None, command_handler.on_invalid_command), 97 | ])) 98 | 99 | async def fake2(): 100 | top_router.routing_table['text'] = command_router.route 101 | 102 | messages = [{'text': '/start'}, 103 | {'text': '/SETTINGS'}, 104 | {'text': '/bad'}, 105 | {'text': 'plain text'}, 106 | {'photo': 'some photo'}, 107 | {'video': 'some video'},] 108 | make_message_like(messages) 109 | 110 | for i in range(0,20): 111 | await top_router.route(random.choice(messages)) 112 | print() 113 | 114 | 115 | class RegexHandler(object): 116 | def on_CS101(self, msg, match): 117 | print('Someone mentioned CS101 !!!', msg, match.groups()) 118 | 119 | def on_CS202(self, msg, match): 120 | print('Someone mentioned CS202 !!!', msg, match.groups()) 121 | 122 | def no_cs_courses_mentioned(self, msg): 123 | print('No CS courses mentioned ...', msg) 124 | 125 | def course_not_exist(self, msg, match): 126 | print('%s does not exist' % match.group(1), msg) 127 | 128 | regex_handler = RegexHandler() 129 | regex_router = telepot.aio.helper.Router(by_regex(lambda msg: msg['text'], '(CS[0-9]{3})'), 130 | make_routing_table(regex_handler, [ 131 | 'CS101', 132 | 'CS202', 133 | ((None,), regex_handler.no_cs_courses_mentioned), 134 | (None, regex_handler.course_not_exist), 135 | ])) 136 | 137 | async def fake3(): 138 | command_router.routing_table[(None,)] = regex_router.route 139 | 140 | messages = [{'text': '/start'}, 141 | {'text': '/SETTINGS'}, 142 | {'text': '/bad'}, 143 | {'text': 'plain text'}, 144 | {'text': 'I want to take CS101.'}, 145 | {'text': 'I\'d rather take CS202.'}, 146 | {'text': 'Why don\'t you take CS303?'}, 147 | {'text': 'I hate computer science!'}, 148 | {'photo': 'some photo'}, 149 | {'video': 'some video'},] 150 | make_message_like(messages) 151 | 152 | for i in range(0,30): 153 | await top_router.route(random.choice(messages)) 154 | print() 155 | 156 | 157 | TOKEN = sys.argv[1] 158 | 159 | bot = telepot.aio.Bot(TOKEN) 160 | bot._router.routing_table['chat'] = top_router.route 161 | 162 | loop = asyncio.get_event_loop() 163 | loop.run_until_complete(fake0()) 164 | loop.run_until_complete(fake1()) 165 | loop.run_until_complete(fake2()) 166 | loop.run_until_complete(fake3()) 167 | 168 | print('Send me some messages ...') 169 | loop.create_task(bot.message_loop()) 170 | loop.run_forever() 171 | -------------------------------------------------------------------------------- /test/test3a_sticker.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | from pprint import pprint 4 | import telepot.aio 5 | from telepot.namedtuple import StickerSet 6 | 7 | async def test_sticker(): 8 | f = await bot.uploadStickerFile(USER_ID, open('gandhi.png', 'rb')) 9 | print('Uploaded Gandhi') 10 | 11 | await bot.addStickerToSet(USER_ID, STICKER_SET, f['file_id'], '\U0001f60a') 12 | await bot.addStickerToSet(USER_ID, STICKER_SET, open('lincoln.png', 'rb'), '\U0001f60a') 13 | print('Added Gandhi and Lincoln to set') 14 | 15 | s = await bot.getStickerSet(STICKER_SET) 16 | pprint(s) 17 | 18 | ss = StickerSet(**s) 19 | 20 | for s in ss.stickers: 21 | await bot.deleteStickerFromSet(s.file_id) 22 | print('Deleted', s.file_id) 23 | await asyncio.sleep(3) # throttle 24 | 25 | s = await bot.getStickerSet(STICKER_SET) 26 | pprint(s) 27 | 28 | TOKEN = sys.argv[1] 29 | USER_ID = int(sys.argv[2]) 30 | STICKER_SET = sys.argv[3] 31 | 32 | bot = telepot.aio.Bot(TOKEN) 33 | loop = asyncio.get_event_loop() 34 | 35 | loop.run_until_complete(test_sticker()) 36 | --------------------------------------------------------------------------------