- Microphone
- Listener
- Sender
- Answerer
- ListenerContext
- ChatContext
- UserContext
- Monitor
- ChatHandler
- UserHandler
- @openable
- per_chat_id
- per_chat_id_in
- per_chat_id_except
- per_from_id
- per_from_id_in
- per_from_id_except
- per_inline_from_id
- per_inline_from_id_in
- per_inline_from_id_except
- call
- create_run
- create_open
telepot.async (Python 3.4.3 or newer)
telepot.async.helper (Python 3.4.3 or newer)
telepot.async.delegate (Python 3.4.3 or newer)
Subclass: telepot.SpeakerBot
This class is mostly a wrapper around Telegram Bot API methods, and is the most ancient part of telepot.
Aside from downloadFile()
and notifyOnMessage()
, all methods are straight mappings from Telegram Bot API. No point to duplicate all the details here. I only give brief descriptions below, and encourage you to visit the underlying API's documentations. Full power of the Bot API can be exploited only by understanding the API itself.
Bot(token)
Use the token to specify the bot.
Examples:
import telepot
bot = telepot.Bot('123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ')
getMe()
Returns basic information about the bot in form of a User object.
See: https://core.telegram.org/bots/api#getme
sendMessage(chat_id, text, parse_mode=None, disable_web_page_preview=None, reply_to_message_id=None, reply_markup=None)
Send text messages.
See: https://core.telegram.org/bots/api#sendmessage
Examples:
# Assuming you have got `chat_id` and `msg_id` from a previously received message by doing:
# msg_id = msg['message_id']
# chat_id = msg['from']['id']
# or
# chat_id = msg['chat']['id']
bot.sendMessage(chat_id, 'A short sentence.')
bot.sendMessage(chat_id, 'I do not understand your last message.', reply_to_message_id=msg_id)
bot.sendMessage(chat_id, 'http://www.yahoo.com \n no web page preview', disable_web_page_preview=True)
# Markdown
bot.sendMessage(chat_id, '*bold text*\n_italic text_\n[link](http://www.google.com)', parse_mode='Markdown')
# HTML
bot.sendMessage(chat_id, '<i>italic</i> <code>inline fixed-width code</code>', parse_mode='HTML')
# Show a custom keyboard
show_keyboard = {'keyboard': [['Yes', 'No'], ['Maybe', 'Maybe not']]}
bot.sendMessage(chat_id, 'Here is a custom keyboard', reply_markup=show_keyboard)
# Hide custom keyboard
hide_keyboard = {'hide_keyboard': True}
bot.sendMessage(chat_id, 'Hiding it now.', reply_markup=hide_keyboard)
# Force reply
force_reply = {'force_reply': True}
bot.sendMessage(chat_id, 'Force reply', reply_markup=force_reply)
# `reply_markup` also accepts namedtuples
from telepot.namedtuple import ReplyKeyboardMarkup, ReplyKeyboardHide, ForceReply
bot.sendMessage(chat_id, 'Show keyboard', reply_markup=ReplyKeyboardMarkup(keyboard=[['Yes', 'No']]))
bot.sendMessage(chat_id, 'Hide keyboard', reply_markup=ReplyKeyboardHide())
bot.sendMessage(chat_id, 'Force reply', reply_markup=ForceReply())
forwardMessage(chat_id, from_chat_id, message_id)
Forward messages of any kind.
See: https://core.telegram.org/bots/api#forwardmessage
sendPhoto(chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None)
Send photos.
See: https://core.telegram.org/bots/api#sendphoto
Examples:
# Uploading a file may take a while. Let user know you are doing something.
bot.sendChatAction(chat_id, 'upload_photo')
# Send a file that is stored locally.
result = bot.sendPhoto(chat_id, open('lighthouse.jpg', 'rb'))
# Get `file_id` from the Message object returned.
file_id = result['photo'][0]['file_id']
# Use `file_id` to resend the same photo, with a caption this time.
bot.sendPhoto(chat_id, file_id, caption='This is a lighthouse')
sendAudio(chat_id, audio, duration=None, performer=None, title=None, reply_to_message_id=None, reply_markup=None)
Send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .mp3 format. For sending voice messages, use sendVoice()
instead.
See: https://core.telegram.org/bots/api#sendaudio
Examples:
# Uploading a file may take a while. Let user know you are doing something.
bot.sendChatAction(chat_id, 'upload_audio')
# Send an audio file that is stored locally.
# Parameter `performer` or `title` has to be set for Telegram to regard it as an audio.
# Otherwise, it will be treated as voice.
result = bot.sendAudio(chat_id, open('dgdg.mp3', 'rb'), title='Ringtone')
# Get `file_id` from the Message object returned.
file_id = result['audio']['file_id']
# Use `file_id` to resend the same audio file.
# Remember to include either `performer` or `title` (or both).
bot.sendAudio(chat_id, file_id, duration=6, performer='Ding Dong')
sendDocument(chat_id, document, reply_to_message_id=None, reply_markup=None)
Send general files.
See: https://core.telegram.org/bots/api#senddocument
Examples:
bot.sendChatAction(chat_id, 'upload_document')
result = bot.sendDocument(chat_id, open('document.txt', 'rb'))
file_id = result['document']['file_id']
bot.sendDocument(chat_id, file_id)
sendSticker(chat_id, sticker, reply_to_message_id=None, reply_markup=None)
Send .webp stickers.
See: https://core.telegram.org/bots/api#sendsticker
Examples:
# I found .png files are also accepted. Other formats? You have to try it yourself.
result = bot.sendSticker(chat_id, open('gandhi.png', 'rb'))
file_id = result['sticker']['file_id']
bot.sendSticker(chat_id, file_id)
sendVideo(chat_id, video, duration=None, caption=None, reply_to_message_id=None, reply_markup=None)
Send video files. Telegram clients support mp4 videos. Other formats may be sent using sendDocument()
.
See: https://core.telegram.org/bots/api#sendvideo
Examples:
bot.sendChatAction(chat_id, 'upload_video')
# Send a video file that is stored locally, with a caption.
result = bot.sendVideo(chat_id, open('hktraffic.mp4', 'rb'), caption='Hong Kong traffic')
file_id = result['video']['file_id']
# Use `file_id` to resend a video file, with a duration stated this time.
bot.sendVideo(chat_id, file_id, duration=5)
sendVoice(chat_id, audio, duration=None, reply_to_message_id=None, reply_markup=None)
Send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .ogg file encoded with OPUS. Other formats may be sent using sendAudio()
or sendDocument()
.
See: https://core.telegram.org/bots/api#sendvoice
Examples:
# Send a voice message that is stored locally.
result = bot.sendVoice(chat_id, open('example.ogg', 'rb'))
file_id = result['voice']['file_id']
# Use `file_id` to resend the voice message, with a duration stated this time.
bot.sendVoice(chat_id, file_id, duration=6)
sendLocation(chat_id, latitude, longitude, reply_to_message_id=None, reply_markup=None)
Send point on the map.
See: https://core.telegram.org/bots/api#sendlocation
Examples:
bot.sendChatAction(chat_id, 'find_location')
bot.sendLocation(chat_id, 22.33, 114.18) # Hong Kong
bot.sendLocation(chat_id, 49.25, -123.1) # Vancouver
bot.sendLocation(chat_id, -37.82, 144.97) # Melbourne
sendChatAction(chat_id, action)
Tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status).
See: https://core.telegram.org/bots/api#sendchataction
Examples: Please refer to examples for sendPhoto()
, sendVideo()
, sendDocument()
, etc.
getUserProfilePhotos(user_id, offset=None, limit=None)
Get a list of profile pictures for a user.
See: https://core.telegram.org/bots/api#getuserprofilephotos
getFile(file_id)
Get a File object, usually as a prelude to downloading a file. If you just want to download a file, call downloadFile()
instead.
See: https://core.telegram.org/bots/api#getfile
getUpdates(offset=None, limit=None, timeout=None)
Receive incoming updates.
See: https://core.telegram.org/bots/api#getupdates
setWebhook(url=None, certificate=None)
Specify a url and receive incoming updates via an outgoing webhook. Optionally, you may upload your public key certificate with the certificate
argument.
See: https://core.telegram.org/bots/api#setwebhook
Examples:
# Webhook without a certificate
bot.setWebhook('https://www.domain.com/webhook')
# Webhook with a certificate
bot.setWebhook('https://www.domain.com/webhook', open('domain.cert', 'rb'))
# Cancel webhook
bot.setWebhook()
downloadFile(file_id, dest)
Download a file. dest
can be a path (string) or a Python file object.
Examples:
bot.downloadFile('ABcdEfGhijkLm_NopQRstuvxyZabcdEFgHIJ', 'save/to/path')
# If you open the file yourself, you are responsible for closing it.
with open('save/to/path', 'wb') as f:
bot.downloadFile('ABcdEfGhijkLm_NopQRstuvxyZabcdEFgHIJ', f)
answerInlineQuery(inline_query_id, results, cache_time=None, is_personal=None, next_offset=None)
Send answers to an inline query. results
is a list of InlineQueryResult. As is the custom in telepot, you may construct those objects as dictionaries. A potentially easier alternative is to use the namedtuple classes provided by telepot.namedtuple
module.
See: https://core.telegram.org/bots/api#answerinlinequery
Examples:
from telepot.namedtuple import InlineQueryResultArticle
articles = [{'type': 'article',
'id': 'abc', 'title': 'ABC', 'message_text': 'Good morning'},
InlineQueryResultArticle(
id='xyz', title='ZZZZZ', message_text='Good night')]
bot.answerInlineQuery(query_id, articles)
from telepot.namedtuple import InlineQueryResultPhoto
photos = [{'type': 'photo',
'id': '123', 'photo_url': '...', 'thumb_url': '...'},
InlineQueryResultPhoto(
id='999', photo_url='...', thumb_url='...')]
bot.answerInlineQuery(query_id, photos)
notifyOnMessage(callback=None, relax=0.1, timeout=20, source=None, ordered=True, maxhold=3, run_forever=False)
Spawn a thread to constantly check for updates. Apply callback
to every message received. callback
must take one argument, which is the message.
If callback
is not supplied, self.handle
is assumed. In other words, a bot must have the method, handle(msg)
, defined if notifyOnMessage()
is called without the callback
argument.
If source
is None
(default), getUpdates()
is used to obtain updates from Telegram servers. If source
is a synchronized queue (Queue.Queue
in Python 2.7 or queue.Queue
in Python 3), updates are obtained from the queue. In normal scenarios, a web application implementing a webhook dumps updates into the queue, while the bot pulls updates from it.
Parameters:
- callback (function): a function to apply to every message received. If
None
,self.handle
is assumed. - relax (float): seconds between each
getUpdates()
. Applies only whensource
isNone
. - timeout (integer): timeout supplied to
getUpdates()
, controlling how long to poll. Applies only whensource
isNone
. - source (Queue): source of updates
- If
None
, usegetUpdates()
to obtain updates from Telegram servers. - If a
Queue
(Queue.Queue
in Python 2.7 orqueue.Queue
in Python 3), updates are pulled from the queue. - Acceptable contents in queue:
str
,unicode
(Python 2.7), orbytes
(Python 3, decoded using UTF-8) representing a JSON-serialized Update object.dict
representing an Update object.
- If
- ordered (boolean): applied only when
source
is aQueue
- If
True
, ensure in-order delivery of updates tocallback
(i.e. updates with a smallerupdate_id
always come before those with a largerupdate_id
). - If
False
, no re-ordering is done.callback
is applied to messages as soon as they are pulled from queue.
- If
- maxhold (float): applied only when
source
is aQueue
andordered
isTrue
- the maximum number of seconds an update is held waiting for a not-yet-arrived smaller
update_id
. When this number of seconds is up, the update is delivered tocallback
even if some smallerupdate_id
s have not yet arrived. If those smallerupdate_id
s arrive at some later time, they are discarded.
- the maximum number of seconds an update is held waiting for a not-yet-arrived smaller
- run_forever (boolean): append an infinite loop at the end and never returns. Useful as the very last line in a program.
Note: source
, ordered
, and maxhold
are relevant only if you use webhook.
This can be a skeleton for a lot of telepot programs:
import sys
import telepot
class YourBot(telepot.Bot):
def handle(self, msg):
content_type, chat_type, chat_id = telepot.glance(msg)
print content_type, chat_type, chat_id
# Do your stuff according to `content_type` ...
TOKEN = sys.argv[1] # get token from command-line
bot = YourBot(TOKEN)
bot.notifyOnMessage(run_forever=True)
If you prefer defining a global handler, or want to have a custom infinite loop at the end, this skeleton may be for you:
import sys
import time
import telepot
def handle(msg):
content_type, chat_type, chat_id = telepot.glance(msg)
print content_type, chat_type, chat_id
# Do your stuff according to `content_type` ...
TOKEN = sys.argv[1] # get token from command-line
bot = telepot.Bot(TOKEN)
bot.notifyOnMessage(handle)
print 'Listening ...'
# Keep the program running.
while 1:
time.sleep(10)
Superclass: telepot.Bot
Subclass: telepot.DelegatorBot
Exposes a Microphone
and lets you create Listener
s who listen to that microphone. You don't have to deal with this class directly, if DelegateBot
satisfies your needs.
SpeakerBot(token)
mic
A Microphone
object. Used to broadcast messages to listeners obtained by create_listener()
.
create_listener()
Returns a Listener
object that listens to the mic
.
Superclass: telepot.SpeakerBot
Can spawn delegates according to delegation patterns specified in the constructor.
DelegatorBot(token, delegation_patterns)
Parameters:
- token: the bot's token
- delegation_patterns: a list of (seed_calculating_function, delegate_producing_function) tuples
seed_calculating_function is a function that takes one argument - the message being processed - and returns a seed. The seed determines whether the following delegate_producing_function is called.
-
If the seed is a hashable (e.g. number, string, tuple), the bot looks for a delegate associated with the seed.
- If such a delegate exists and is alive, it is assumed that the message will be picked up by the delegate. The bot does nothing.
- If no delegate exists or that delegate is no longer alive, the bot spawns a new delegate by calling delegate_producing_function and associates the new delegate with the seed.
-
If the seed is a non-hashable (e.g. list), the bot always spawns a new delegate by calling delegate_producing_function. No seed-delegate association occurs.
-
If the seed is
None
, nothing is done.
In essence, only one delegate is running for a given seed, if that seed is a hashable.
delegate_producing_function is a function that takes one argument - a tuple of (bot, message, seed) - and returns a delegate. A delegate can be one of the following:
- an object that has a
start()
andis_alive()
method. Therefore, athreading.Thread
object is a natural delegate. Once theobject
is obtained,object.start()
is called. - a
function
, in which case it is wrapped by aThread(target=function)
and started. - a tuple of
(function, args, kwargs)
, in which case it is wrapped by aThread(target=function, args=args, kwargs=kwargs)
and started.
All seed_calculating_functions are evaluated in order. One message may cause multiple delegates to be spawned.
This class implements the above logic in its handle
method. Once you supply a list of (seed_calculating_function, delegate_producing_function) pairs to the constructor and invoke notifyOnMessage()
, the above logic will be executed for every message received.
Even if you use a webhook and don't need notifyOnMessage()
, you may always call bot.handle(msg)
directly to take advantage of the above logic, if you find it useful.
The power of delegation is most easily exploited when used in combination with the telepot.delegate
module (which contains a number of ready-made seed_calculating_functions and delegate_producing_functions) and the ChatHandler
class (which provides a connection-like interface to deal with individual chats).
Here is a bot that counts how many messages it has received in a chat. If no message is received after 10 seconds, it starts over. The counting is done per chat - that's the important point.
import sys
import telepot
from telepot.delegate import per_chat_id, create_open
class MessageCounter(telepot.helper.ChatHandler):
def __init__(self, seed_tuple, timeout):
super(MessageCounter, self).__init__(seed_tuple, timeout)
self._count = 0
def on_message(self, msg):
self._count += 1
self.sender.sendMessage(self._count)
TOKEN = sys.argv[1] # get token from command-line
bot = telepot.DelegatorBot(TOKEN, [
(per_chat_id(), create_open(MessageCounter, timeout=10)),
])
bot.notifyOnMessage(run_forever=True)
Thanks to Tornado for inspiration.
flavor(msg)
Returns the flavor of a message:
- a normal message is
normal
- an inline query is
inline_query
- a chosen inline result is
chosen_inline_result
If the bot can receive inline queries and/or chosen inline results, you should always check the flavor before further processing. See glance()
below for an example.
glance(msg, flavor='normal', long=False)
This function has an alias, glance2()
, for backward compatibility. Developers are encouraged to use glance()
from now on.
Extracts "headline" information about a message.
When flavor
is normal
:
- returns a tuple of (content_type, chat_type, msg['chat']['id']).
- if
long
isTrue
, returns a tuple of (content_type, chat_type, msg['chat']['id'], msg['date'], msg['message_id']).
content_type can be one of: text
, voice
, sticker
, photo
, audio
, document
, video
, contact
, location
, new_chat_participant
, left_chat_participant
, new_chat_title
, new_chat_photo
, delete_chat_photo
, group_chat_created
, supergroup_chat_created
, migrate_to_chat_id
, migrate_from_chat_id
, or channel_chat_created
.
chat_type can be one of: private
, group
, supergroup
, or channel
.
When flavor
is inline_query
:
- returns a tuple of (msg['id'], msg['from']['id'], msg['query']).
- if
long
isTrue
, returns a tuple of (msg['id'], msg['from']['id'], msg['query'], msg['offset']).
When flavor
is chosen_inline_result
:
- returns a tuple of (msg['result_id'], msg['from']['id'], msg['query'])
- parameter
long
has no effect in this case
Examples:
import telepot
# Suppose `msg` is a message previously received.
flavor = telepot.flavor(msg)
if flavor == 'message':
content_type, chat_type, chat_id = telepot.glance(msg)
content_type, chat_type, chat_id, msg_date, msg_id = telepot.glance(msg, long=True)
# Do you stuff according to `content_type`
elif flavor == 'inline_query':
query_id, from_id, query_string = telepot.glance(msg, flavor='inline_query')
query_id, from_id, query_string, offset = telepot.glance(msg, flavor='inline_query', long=True)
# bot.answerInlineQuery(...)
elif flavor == 'chosen_inline_result':
result_id, from_id, query_string = telepot.glance(msg, flavor='chosen_inline_result')
# remember the chosen result, hopefully do better next time
flance(msg, long=False)
A combination of flavor()
and glance()
, it returns a tuple of two elements: (flavor of message, tuple as returned by glance()
of the same message). The long
parameter is passed to glance()
, controlling how much info is extracted.
Telepot's custom is to represent Bot API objects as dictionaries. On the other hand, we also have namedtuple classes mirroring all Bot API object types:
- User
- Chat
- Message
- PhotoSize
- Audio
- Document
- Sticker
- Video
- Voice
- Contact
- Location
- Update
- UserProfilePhotos
- File
- ReplyKeyboardMarkup
- ReplyKeyboardHide
- ForceReply
- InlineQuery
- InlineQueryResultArticle
- InlineQueryResultPhoto
- InlineQueryResultGif
- InlineQueryResultMpeg4Gif
- InlineQueryResultVideo
The reasons for having namedtuple classes are twofold:
- Under some situations, you may want an object with a complete set of fields, including those whose values are
None
. A dictionary, as translated from Bot API's response, would have thoseNone
fields absent. By converting such a dictionary to a namedtuple, all fields are guaranteed to be present, even if their values areNone
. - While you may construct
ReplyKeyboardMarkup
,ReplyKeyboardHide
,ForceReply
, andInlineQueryResultZZZ
using dictionaries, it may be easier to use the namedtuple constructors.
Beware of a peculiarity. Namedtuple field names cannot be Python keywords, but the Message object and a few others have a from
field, which is a Python keyword. I choose to append an underscore to it. That is, the dictionary value dict['from']
becomes namedtuple.from_
when converted to a namedtuple.
Suppose you have received a message in the form a dictionary, here are two ways to obtain its namedtuple:
from telepot.namedtuple import namedtuple, Message
# Unpack dict, give it to constructor
ntuple1 = Message(**msg)
# Use namedtuple() function, give it the class name
ntuple2 = namedtuple(msg, 'Message')
# ntuple1 == ntuple2
# `from` becomes `from_` due to keyword collision
print ntuple1.from_.id # == msg['from']['id']
# other field names unchanged
print ntuple2.chat.id # == msg['chat']['id']
You may also choose to convert only part of the message:
from telepot.namedtuple import namedtuple, User, Chat
ntuple3 = User(**msg['from']) # these two lines
ntuple4 = namedtuple(msg['from'], 'User') # are equivalent
ntuple5 = Chat(**msg['chat']) # these two lines
ntuple6 = namedtuple(msg['chat'], 'Chat') # are equivalent
namedtuple(dict, type)
Convert a dictionary to a namedtuple of a given object type.
type can be one of the Bot API object types listed above.
When creating reply markup (for sendZZZ()
) or inline query results (for answerInlineQuery()
), telepot's custom is to use dictionaries. An alternative is to use namedtuples.
Examples:
from telepot.namedtuple import ReplyKeyboardMarkup, ReplyKeyboardHide, ForceReply
bot.sendMessage(chat_id, 'Show keyboard', reply_markup=ReplyKeyboardMarkup(keyboard=[['Yes', 'No']]))
bot.sendMessage(chat_id, 'Hide keyboard', reply_markup=ReplyKeyboardHide())
bot.sendMessage(chat_id, 'Force reply', reply_markup=ForceReply())
from telepot.namedtuple import InlineQueryResultArticle
articles = [{'type': 'article',
'id': 'abc', 'title': 'ABC', 'message_text': 'Good morning'},
InlineQueryResultArticle(
id='xyz', title='ZZZZZ', message_text='Good night')]
bot.answerInlineQuery(query_id, articles)
from telepot.namedtuple import InlineQueryResultPhoto
photos = [{'type': 'photo',
'id': '123', 'photo_url': '...', 'thumb_url': '...'},
InlineQueryResultPhoto(
id='999', photo_url='...', thumb_url='...')]
bot.answerInlineQuery(query_id, photos)
A Microphone
, when the send()
method is called, puts messages into each Listener
's message queue.
Normally, you should not need to create this object, but obtain it using SpeakerBot.mic
.
Microphone()
add(queue)
Add a listener's message queue.
remove(queue)
Remove a listener's message queue.
send(msg)
Puts msg
into each listener's message queue.
Subclass: telepot.async.helper.Listener
Used to suspend execution until a certain message appears. Users can specify how to "match" for messages in the capture()
method.
Normally, you should not need to create this object, but obtain it using SpeakerBot.create_listener()
or access it with ChatHandler.listener
.
Listener(microphone, queue)
wait()
Blocks until a "matched" message appears, and returns that message. See capture()
for how to specify match conditions.
If the timeout option is set, it will raise a WaitTooLong
exception after that many seconds.
If the timeout option is not set or is None
(default), it will wait forever.
set_options(**name_values)
Available options:
- timeout - how many seconds to
wait
for a matched message
Example:
listener.set_options(timeout=10)
get_options(*names)
Returns a tuple of values of those option names.
Example:
timeout, = listener.get_options('timeout')
capture(**criteria)
Add a capture criteria.
When the wait()
method sees a message that matches the criteria, it returns that message.
The capture()
method may be called multiple times, resulting in a list of criteria. A message is considered a match if any one of the those criteria is satisfied.
The format of criteria is best illustrated with some examples:
# look for messages whose `message['chat']['id']` equals `12345678`
listener.capture(chat__id=12345678)
# equivalent to above
listener.capture(chat={'id': 12345678})
def looks_like_integer(s):
try:
int(s)
return True
except:
return False
# look for messages where `looks_like_integer(message['text'])` returns true
listener.capture(text=looks_like_integer)
# could be done more simply as ...
listener.capture(text=lambda s: s.isdigit())
# look for messages that satisfy BOTH conditions
listener.capture(text=lambda s: s.isdigit(), chat__id=12345678)
def sender_sounds_like_me(msg):
return msg['from']['first_name'].startswith('Nick') and msg['from']['last_name'].startswith('Lee')
# looks for messages where `sender_sounds_like_me(message)` returns true
# use '_' to select the entire message
listener.capture(_=sender_sounds_like_me)
For each keyword arguments:
-
the key is used to select a part of message
- use a double__underscore to "get down a level", e.g.
from
selectsmsg['from']
,chat__id
selectsmsg['chat']['id']
- use a
_
to select the entire message
- use a double__underscore to "get down a level", e.g.
-
the value is a "template", may be one of the following:
- a simple value
- a function that checks the match
- takes one argument - the part of message selected by the key
- returns
True
to indicate a match
- a dictionary to further select parts of message
When more than one keyword argument are given, all conditions have to be satisfied.
Thanks to Django for inspiration.
cancel_capture(**criteria)
Remove a capture criteria. To remove a previously added criteria, the form of criteria given here must be exactly the same as that given to capture()
previously.
clear_captures()
Remove all capture criteria.
list_captures()
List all capture criteria.
A proxy to a bot's sendZZZ()
and forwardMessage()
methods, with a fixed chat_id
to save having to supply it every time.
Normally, you should not need to create this object, but access it with ChatHandler.sender
.
Sender(bot, chat_id)
Parameters:
- bot - the parent bot
- chat_id - the default chat id. All messages will be aimed at this chat id.
sendMessage(text, parse_mode=None, disable_web_page_preview=None, reply_to_message_id=None, reply_markup=None)
forwardMessage(from_chat_id, message_id)
sendPhoto(photo, caption=None, reply_to_message_id=None, reply_markup=None)
sendAudio(audio, duration=None, performer=None, title=None, reply_to_message_id=None, reply_markup=None)
sendDocument(document, reply_to_message_id=None, reply_markup=None)
sendSticker(sticker, reply_to_message_id=None, reply_markup=None)
sendVideo(video, duration=None, caption=None, reply_to_message_id=None, reply_markup=None)
sendVoice(audio, duration=None, reply_to_message_id=None, reply_markup=None)
sendLocation(latitude, longitude, reply_to_message_id=None, reply_markup=None)
sendChatAction(action)
On receiving an inline query, it spawns a thread to compute results and send them. If a preceding thread is already working for a user, it is cancelled. This ensures at most one active thread per user id.
Answerer(bot, compute_function)
Parameters:
- bot - the parent bot.
- compute_function - an answer-computing function.
- It must take one argument, the inline query.
- Its returned value is given to
bot.answerInlineQuery()
to send. - It may return one of the following:
- a list of InlineQueryResult
- a tuple, whose first element is a list of InlineQueryResult, followed by positional arguments to be supplied to
bot.answerInlineQuery()
- a dict representing keyword arguments to be supplied to
bot.answerInlineQuery()
- It must be thread-safe, because many threads may access it as the same time.
answer(inline_query)
Spawns a thread that calls the compute_function
(specified in constructor), then applies the returned value to bot.answerInlineQuery()
, in effect answering the inline query. If a preceding thread is already working for a user, that thread is cancelled, thus ensuring at most one active thread per user id.
Subclass: telepot.helper.ChatContext
telepot.helper.UserContext
telepot.helper.Monitor
ListenerContext(bot, context_id)
Parameters:
- bot - the parent bot. Should be a
SpeakerBot
or one of its subclasses. - context_id - this ID's purpose and uniqueness to up to the user.
This object exposes these properties:
- bot
- id - the context ID
- listener - a
Listener
object
Superclass: telepot.helper.ListenerContext
Subclass: telepot.helper.ChatHandler
ChatContext(bot, context_id, chat_id)
Parameters:
- bot - the parent bot. Should be a
SpeakerBot
or one of its subclasses. - context_id - this ID's purpose and uniqueness to up to the user.
- chat_id - the target chat
This object exposes these properties:
- chat_id
- sender - a
Sender
object aimed at the target chat
Superclass: telepot.helper.ListenerContext
Subclass: telepot.helper.UserHandler
UserContext(bot, context_id, user_id)
Parameters:
- bot - the parent bot. Should be a
SpeakerBot
or one of its subclasses. - context_id - this ID's purpose and uniqueness to up to the user.
- user_id - the target user's id
This object exposes these properties:
- user_id
- sender - a
Sender
object aimed at the target user
Superclass: telepot.helper.ListenerContext
How to use this class:
- Extend this class
- Implement
on_message()
, optionally overrideopen()
andon_close()
- Use
telepot.delegate.create_open()
to plug it into aDelegatorBot
Monitor(seed_tuple, capture)
Parameters:
- seed_tuple - a tuple of (bot, message, seed) generated by the delegation mechanism
- capture - a list of capture criteria for this object's
listener
. SeeListener
for syntax of how to compose criteria.
In the following example:
- a
MessagePrinter
(subclass ofMonitor
) is set up to capture all messages - a bot is set up to spawn one and only one
MessagePrinter
import sys
import telepot
from telepot.delegate import create_open
class MessagePrinter(telepot.helper.Monitor):
def __init__(self, seed_tuple):
# Returns true for every message, meaning all are captured.
capture_criteria = {'_': lambda msg: True}
super(MessagePrinter, self).__init__(seed_tuple, [capture_criteria])
def on_message(self, msg):
print('Message received:')
print(msg)
TOKEN = sys.argv[1]
bot = telepot.DelegatorBot(TOKEN, [
# Seed is always the same, meaning only one MessagePrinter is ever spawned.
(lambda msg: 1, create_open(MessagePrinter)),
])
bot.notifyOnMessage(run_forever=True)
open(initial_msg, seed)
Called when initially spawned.
on_message(msg)
Called when a message is captured by this object's listener
. Subclass must implement.
on_close(exception)
Called just before this object is about to exit.
close(code=None, reason=None)
Raises a StopListening
exception, causing this object to exit.
Superclass: telepot.helper.ChatContext
How to use this class:
- Extend this class
- Implement
on_message()
, optionally overrideopen()
andon_close()
- Use
telepot.delegate.create_open()
to plug it into aDelegatorBot
ChatHandler(seed_tuple, timeout)
Parameters:
- seed_tuple - a tuple of (bot, message, seed) generated by the delegation mechanism
- timeout - timeout for this object's
listener
This object's listener
is automatically set up to capture messages from the same chat id as the initial message, contained in the seed_tuple parameter.
Here is a bot that counts how many messages it has received in a chat. If no message is received after 10 seconds, it starts over. The counting is done per chat - that's the important point.
import sys
import telepot
from telepot.delegate import per_chat_id, create_open
class MessageCounter(telepot.helper.ChatHandler):
def __init__(self, seed_tuple, timeout):
super(MessageCounter, self).__init__(seed_tuple, timeout)
self._count = 0
def on_message(self, msg):
self._count += 1
self.sender.sendMessage(self._count)
TOKEN = sys.argv[1] # get token from command-line
bot = telepot.DelegatorBot(TOKEN, [
(per_chat_id(), create_open(MessageCounter, timeout=10)),
])
bot.notifyOnMessage(run_forever=True)
open(initial_msg, seed)
Called when initially spawned.
on_message(msg)
Called when a message is captured by this object's listener
. Subclass must implement.
on_close(exception)
Called just before this object is about to exit.
close(code=None, reason=None)
Raises a StopListening
exception, causing this object to exit.
Superclass: telepot.helper.UserContext
How to use this class:
- Extend this class
- Implement
on_message()
, optionally overrideopen()
andon_close()
- Use
telepot.delegate.create_open()
to plug it into aDelegatorBot
UserHandler(seed_tuple, timeout, flavors='all')
Parameters:
- seed_tuple - a tuple of (bot, message, seed) generated by the delegation mechanism
- timeout - timeout for this object's
listener
- flavors - which flavors of messages to listen for. May be
all
(default), or a list of flavors.
This object's listener
is automatically set up to capture messages of the specified flavors
and from the same user as the initial message's, contained in the seed_tuple parameter.
open(initial_msg, seed)
Called when initially spawned.
on_message(msg)
Called when a message is captured by this object's listener
. Subclass must implement.
on_close(exception)
Called just before this object is about to exit.
close(code=None, reason=None)
Raises a StopListening
exception, causing this object to exit.
This class decorator supplies methods required by create_open()
, if they are not defined by the class. It simplifies efforts to create a class to be used with create_open()
.
The following methods (and their contents) are supplied, if they are not defined by the class already:
- method open(initial_msg, seed) - empty
- method on_message(msg) - raise a
NotImplementedError
- method on_close(exception) - log the exception (default: print to
stderr
), better for development because the exception would not be swallowed silently. - method close(code=None, reason=None) - raise a
StopListening
exception, causing exit. - property listener - raise a
NotImplementedError
In other words, a class decorated by @openable
still has to implement the method on_message(msg)
and the property listener
.
The best demonstration of using @openable
is actually the Monitor
and ChatHandler
class themselves. They inherit a listener
property from their superclasses; the use of @openable
fills in other methods, but leaves the listener
property untouched. Defining the class is only a matter of giving it a constructor.
@openable
class Monitor(ListenerContext):
def __init__(...):
...
@openable
class ChatHandler(ChatContext):
def __init__(...):
...
This module provides functions used in conjunction with DelegatorBot
to specify delegation patterns. See DelegatorBot
for more details.
Returns a seed-calculating-function that returns the chat id as the seed, equivalent to:
lambda msg: msg['chat']['id']
Returns a seed-calculating-function that returns the chat id as the seed if the chat id is in the set
, equivalent to:
lambda msg: msg['chat']['id'] if msg['chat']['id'] in set else None
Returns a seed-calculating-function that returns the chat id as the seed if the chat id is not in the set
, equivalent to:
lambda msg: msg['chat']['id'] if msg['chat']['id'] not in set else None
Returns a seed-calculating-function that returns from
field's user id as the seed, equivalent to:
lambda msg: msg['from']['id']
Returns a seed-calculating-function that returns from
field's user id as the seed, if that user id is in the set
, equivalent to:
lambda msg: msg['from']['id'] if msg['from']['id'] in set else None
Returns a seed-calculating-function that returns from
field's user id as the seed, if that user id is not in the set
, equivalent to:
lambda msg: msg['from']['id'] if msg['from']['id'] not in set else None
Returns a seed-calculating-function that returns from
field's user id as the seed, only for inline queries and chosen inline results.
Returns a seed-calculating-function that returns from
field's user id as the seed, if that user id is in the set
, only for inline queries and chosen inline results.
per_inline_from_id_except(set)
Returns a seed-calculating-function that returns from
field's user id as the seed, if that user id is not in the set
, only for inline queries and chosen inline results.
Returns a delegate-producing-function that returns a tuple of (func, (seed_tuple,)+args, kwargs)
, causing DelegatorBot
to spawn a thread around that function and those arguments. func
should take a seed_tuple as the first argument, followed by those explicitly supplied. Here is the source:
def call(func, *args, **kwargs):
def f(seed_tuple):
return func, (seed_tuple,)+args, kwargs
return f
create_run(cls, *args, **kwargs)
Returns a delegate-producing-function that creates an object of cls
and returns its run
method, causing DelegatorBot
to spawn a thread around its run
method. The cls
constructor should take a seed_tuple as the first argument, followed by those explicitly supplied. The run
method should take no argument. Here is the source:
def create_run(cls, *args, **kwargs):
def f(seed_tuple):
j = cls(seed_tuple, *args, **kwargs)
return j.run
return f
create_open(cls, *args, **kwargs)
Returns a delegate-producing-function that creates an object of cls
, then engages it in the following manner:
- call its
open(initial_msg, seed)
method - in an infinite loop:
- call its
listener.wait()
, blocking until the next message captured - call its
on_message(msg)
method
- call its
- if any exception is raised, call its
close(exception)
method, then exit
The object of cls
must have these defined:
- a constructor that takes a seed_tuple as the first argument, followed by those explicitly supplied in the
create_open()
call - method
open(initial_msg, seed)
- method
on_message(msg)
- method
on_close(exception)
- property
listener
which returns aListener
object
An easy way to fulfilled these requirements is to extend from Monitor
or ChatHandler
, or decorating a class with the @openable
class decorator.
Here is the source for reference:
def create_open(cls, *args, **kwargs):
def f(seed_tuple):
j = cls(seed_tuple, *args, **kwargs)
def wait_loop():
bot, msg, seed = seed_tuple
try:
handled = j.open(msg, seed)
if not handled:
j.on_message(msg)
while 1:
msg = j.listener.wait()
j.on_message(msg)
except Exception as e:
j.on_close(e)
return wait_loop
return f
This package mirrors the traditional version of telepot to make use of the asyncio
module of Python 3.4. Nearly all methods share identical signatures with their traditional siblings, except that blocking methods now become coroutines and are often called with yield from
.
If you find this part of documentations wanting, always refer back to the traditional counterparts. It is easy to adapt examples from there to here - just remember to yield from
coroutines.
Subclass: telepot.async.SpeakerBot
Bot(token, loop=None)
Use the token to specify the bot. If no loop
is given, it uses asyncio.get_event_loop()
to get the default event loop.
loop
This bot's event loop.
coroutine getMe()
Returns basic information about the bot in form of a User object.
See: https://core.telegram.org/bots/api#getme
coroutine sendMessage(chat_id, text, parse_mode=None, disable_web_page_preview=None, reply_to_message_id=None, reply_markup=None)
Send text messages.
See: https://core.telegram.org/bots/api#sendmessage
coroutine forwardMessage(chat_id, from_chat_id, message_id)
Forward messages of any kind.
See: https://core.telegram.org/bots/api#forwardmessage
coroutine sendPhoto(chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None)
Send photos.
See: https://core.telegram.org/bots/api#sendphoto
coroutine sendAudio(chat_id, audio, duration=None, performer=None, title=None, reply_to_message_id=None, reply_markup=None)
Send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .mp3 format. For sending voice messages, use sendVoice()
instead.
See: https://core.telegram.org/bots/api#sendaudio
coroutine sendDocument(chat_id, document, reply_to_message_id=None, reply_markup=None)
Send general files.
See: https://core.telegram.org/bots/api#senddocument
coroutine sendSticker(chat_id, sticker, reply_to_message_id=None, reply_markup=None)
Send .webp stickers.
See: https://core.telegram.org/bots/api#sendsticker
coroutine sendVideo(chat_id, video, duration=None, caption=None, reply_to_message_id=None, reply_markup=None)
Send video files. Telegram clients support mp4 videos. Other formats may be sent using sendDocument()
.
See: https://core.telegram.org/bots/api#sendvideo
coroutine sendVoice(chat_id, audio, duration=None, reply_to_message_id=None, reply_markup=None)
Send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .ogg file encoded with OPUS. Other formats may be sent using sendAudio()
or sendDocument()
.
See: https://core.telegram.org/bots/api#sendvoice
coroutine sendLocation(chat_id, latitude, longitude, reply_to_message_id=None, reply_markup=None)
Send point on the map.
See: https://core.telegram.org/bots/api#sendlocation
coroutine sendChatAction(chat_id, action)
Tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status).
See: https://core.telegram.org/bots/api#sendchataction
coroutine getUserProfilePhotos(user_id, offset=None, limit=None)
Get a list of profile pictures for a user.
See: https://core.telegram.org/bots/api#getuserprofilephotos
coroutine getFile(file_id)
Get a File object, usually as a prelude to downloading a file. If you just want to download a file, call downloadFile()
instead.
See: https://core.telegram.org/bots/api#getfile
coroutine getUpdates(offset=None, limit=None, timeout=None)
Receive incoming updates.
See: https://core.telegram.org/bots/api#getupdates
coroutine setWebhook(url=None, certificate=None)
Specify a url and receive incoming updates via an outgoing webhook.
See: https://core.telegram.org/bots/api#setwebhook
coroutine downloadFile(file_id, dest)
Download a file. dest
can be a path (string) or a Python file object.
coroutine answerInlineQuery(self, inline_query_id, results, cache_time=None, is_personal=None, next_offset=None)
To be filled in ...
coroutine messageLoop(handler=None, source=None, ordered=True, maxhold=3)
Functionally equivalent to notifyOnMessage()
, this method constantly checks for updates and applies handler
to each message received. handler
must take one argument, which is the message.
If handler
is None
, self.handle
is assumed to be the handler function. In other words, a bot must have the method, handle(msg)
, defined if messageLoop()
is called without the handler
argument.
If source
is None
(default), getUpdates()
is used to obtain updates from Telegram servers. If source
is an asyncio.Queue
, updates are obtained from the queue. In normal scenarios, a web application implementing a webhook dumps updates into the queue, while the bot pulls updates from it.
Parameters:
- handler: a function or coroutine to apply to every message received. If
None
,self.handle
is assumed.- If a regular function, it is called directly.
- If a coroutine, it is allocated a task using
BaseEventLoop.create_task()
.
- source (Queue): source of updates
- If
None
, usegetUpdates()
to obtain updates from Telegram servers. - If an
asyncio.Queue
, updates are pulled from the queue. - Acceptable contents in queue:
str
orbytes
(decoded using UTF-8) representing a JSON-serialized Update object.dict
representing an Update object.
- If
- ordered (boolean): applied only when
source
is a queue- If
True
, ensure in-order delivery of updates tohandler
(i.e. updates with a smallerupdate_id
always come before those with a largerupdate_id
). - If
False
, no re-ordering is done.handler
is applied to messages as soon as they are pulled from queue.
- If
- maxhold (float): applied only when
source
is a queue andordered
isTrue
- the maximum number of seconds an update is held waiting for a not-yet-arrived smaller
update_id
. When this number of seconds is up, the update is delivered tohandler
even if some smallerupdate_id
s have not yet arrived. If those smallerupdate_id
s arrive at some later time, they are discarded.
- the maximum number of seconds an update is held waiting for a not-yet-arrived smaller
Note: source
, ordered
, and maxhold
are relevant only if you use webhook.
This can be a skeleton for a lot of telepot programs:
import sys
import asyncio
import telepot
import telepot.async
class YourBot(telepot.async.Bot):
@asyncio.coroutine
def handle(self, msg):
content_type, chat_type, chat_id = telepot.glance(msg)
print(content_type, chat_type, chat_id)
# Do your stuff according to `content_type` ...
TOKEN = sys.argv[1] # get token from command-line
bot = YourBot(TOKEN)
loop = asyncio.get_event_loop()
loop.create_task(bot.messageLoop())
print('Listening ...')
loop.run_forever()
Or, if you prefer a global handler:
import sys
import asyncio
import telepot
import telepot.async
@asyncio.coroutine
def handle(msg):
content_type, chat_type, chat_id = telepot.glance(msg)
print(content_type, chat_type, chat_id)
# Do your stuff according to `content_type` ...
TOKEN = sys.argv[1] # get token from command-line
bot = telepot.async.Bot(TOKEN)
loop = asyncio.get_event_loop()
loop.create_task(bot.messageLoop(handle))
print('Listening ...')
loop.run_forever()
Superclass: telepot.async.Bot
Subclass: telepot.async.DelegatorBot
Exposes a Microphone
and lets you create Listener
s who listen to that microphone. You don't have to deal with this class directly, if DelegateBot
and ChatHandler
satisfy your needs.
SpeakerBot(token, loop=None)
mic
A Microphone
object. Used to broadcast messages to listeners obtained by create_listener()
.
create_listener()
Returns a Listener
object that listens to the mic
.
Superclass: telepot.async.SpeakerBot
Can create tasks according to delegation patterns specified in the constructor.
Unlike its traditional counterpart, this class uses coroutine and task to achieve delegation. To understand the remaining discussions, you have to understand asyncio's tasks and coroutines, especially the difference between a coroutine function and a coroutine object.
DelegatorBot(token, delegation_patterns, loop=None)
Parameters:
- token: the bot's token
- delegation_patterns: a list of (seed_calculating_function, coroutine_producing_function) tuples
- loop: the event loop
seed_calculating_function is a function that takes one argument - the message being processed - and returns a seed. The seed determines whether the following coroutine_producing_function is called.
-
If the seed is a hashable (e.g. number, string, tuple), the bot looks for a task associated with the seed.
- If such a task exists and is not done, it is assumed that the message will be picked up by the task. The bot does nothing.
- If no task exists or that task is done, the bot calls coroutine_producing_function, use the returned coroutine object to create a task, and associates the new task with the seed.
-
If the seed is a non-hashable (e.g. list), the bot always calls coroutine_producing_function, and use the returned coroutine object to create a task. No seed-task association occurs.
-
If the seed is
None
, nothing is done.
In essence, only one task is running for a given seed, if that seed is a hashable.
coroutine_producing_function is a function that takes one argument - a tuple of (bot, message, seed) - and returns a coroutine object, which will be used to create a task.
All seed_calculating_functions are evaluated in order. One message may cause multiple tasks to be created.
This class implements the above logic in its handle
method. Once you supply a list of (seed_calculating_function, coroutine_producing_function) pairs to the constructor and invoke messageLoop()
, the above logic will be executed for every message received.
Even if you use a webhook and don't need messageLoop()
, you may always call bot.handle(msg)
directly to take advantage of the above logic, if you find it useful.
The power of delegation is most easily exploited when used in combination with the telepot.delegate
module (which contains a number of ready-made seed_calculating_functions), the telepot.async.delegate
module (which contains a number of ready-made coroutine_producing_functions), and the ChatHandler
class (which provides a connection-like interface to deal with individual chats).
Here is a bot that counts how many messages it has received in a chat. If no message is received after 10 seconds, it starts over. The counting is done per chat - that's the important point.
import sys
import asyncio
import telepot
from telepot.delegate import per_chat_id
from telepot.async.delegate import create_open
class MessageCounter(telepot.helper.ChatHandler):
def __init__(self, seed_tuple, timeout):
super(MessageCounter, self).__init__(seed_tuple, timeout)
self._count = 0
@asyncio.coroutine
def on_message(self, msg):
self._count += 1
yield from self.sender.sendMessage(self._count)
TOKEN = sys.argv[1] # get token from command-line
bot = telepot.async.DelegatorBot(TOKEN, [
(per_chat_id(), create_open(MessageCounter, timeout=10)),
])
loop = asyncio.get_event_loop()
loop.create_task(bot.messageLoop())
print('Listening ...')
loop.run_forever()
A Microphone
, when the send()
method is called, puts messages into each Listener
's message queue.
Normally, you should not need to create this object, but access it using SpeakerBot.mic
.
The only difference with traditional telepot.helper.Microphone
is that it uses asyncio.Queue
instead of the concurrent Queue
.
Microphone()
add(queue)
Add a listener's message queue.
remove(queue)
Remove a listener's message queue.
send(msg)
Puts msg
into each listener's message queue.
Superclass: telepot.helper.Listener
Used to suspend execution until a certain message appears. Users can specify how to "match" for messages in the capture()
method. See telepot.helper.Listener
for details.
Normally, you should not need to create this object, but obtain it using SpeakerBot.create_listener()
or access it with ChatHandler.listener
.
The only difference with traditional telepot.helper.Listener
is that it uses asyncio.Queue
instead of the concurrent Queue
, and the wait
method becomes a coroutine.
Listener(microphone, queue)
coroutine wait()
On receiving an inline query, it creates a new task to compute results and send them. If a preceding task is already working for a user, it is cancelled. This ensures at most one active task per user id.
Answerer(bot, compute_function, loop=None)
Parameters:
- bot - the parent bot.
- compute_function - an answer-computing function.
- It may be a regular function or a coroutine.
- It must take one argument, the inline query.
- Its returned value is given to
bot.answerInlineQuery()
to send. - It may return one of the following:
- a list of InlineQueryResult
- a tuple, whose first element is a list of InlineQueryResult, followed by positional arguments to be supplied to
bot.answerInlineQuery()
- a dict representing keyword arguments to be supplied to
bot.answerInlineQuery()
- loop - the event loop. If
None
, useasyncio
's default event loop.
answer(inline_query)
Creates a task that calls the compute_function
(specified in constructor), then applies the returned value to bot.answerInlineQuery()
, in effect answering the inline query. If a preceding task is already working for a user, that task is cancelled, thus ensuring at most one active task per user id.
This module provides functions used in conjunction with telepot.async.DelegatorBot
to specify delegation patterns. See telepot.async.DelegatorBot
for more details.
call(corofunc, *args, **kwargs)
Returns a coroutine-producing-function. corofunc
should be a coroutine function that takes a seed_tuple as the first argument, followed by those explicitly supplied. Here is the source:
def call(corofunc, *args, **kwargs):
def f(seed_tuple):
return corofunc(seed_tuple, *args, **kwargs)
return f
create_run(cls, *args, **kwargs)
Returns a coroutine-producing-function that creates an object of cls
and returns a coroutine object by calling its run()
method. The cls
constructor should take a seed_tuple as the first argument, followed by those explicitly supplied. The run
method should be a coroutine function that takes no argument. Here is the source:
def create_run(cls, *args, **kwargs):
def f(seed_tuple):
j = cls(seed_tuple, *args, **kwargs)
return j.run()
return f
create_open(cls, *args, **kwargs)
Returns a coroutine-producing-function that creates an object of cls
, then engages it in the following manner:
- call its
open(initial_msg, seed)
method - in an infinite loop:
- call its
listener.wait()
, suspended until the next message captured - call its
on_message(msg)
method
- call its
- if any exception is raised, call its
close(exception)
method, then exit
The object of cls
must have these defined:
- a constructor that takes a seed_tuple as the first argument, followed by those explicitly supplied in the
create_open()
call - method (regular or coroutine)
open(initial_msg, seed)
- method (regular or coroutine)
on_message(msg)
- method (regular or coroutine)
on_close(exception)
- property
listener
which returns aListener
object
An easy way to fulfilled these requirements is to extend from Monitor
or ChatHandler
, or decorating a class with the @openable
class decorator.