The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .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 = {'<': '&lt;',
78 |                '>': '&gt;',
79 |                '&': '&amp;',}
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> &lt;b&gt;bold&lt;/b&gt;
68 | <i>2.</i> &lt;i&gt;italic&lt;/i&gt;
69 | 3. &lt;a href="http://www.example.com/"&gt;inline URL&lt;/a&gt;
70 | <code>4.</code> &lt;code&gt;inline fixed-width code&lt;/code&gt;''',
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> &lt;b&gt;bold&lt;/b&gt;
68 | <i>2.</i> &lt;i&gt;italic&lt;/i&gt;
69 | 3. &lt;a href="http://www.example.com/"&gt;inline URL&lt;/a&gt;
70 | <code>4.</code> &lt;code&gt;inline fixed-width code&lt;/code&gt;''',
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 | 


--------------------------------------------------------------------------------