diff --git a/examples/chatbox_nodb.py b/examples/chatbox_nodb.py index 1b9ffc1..60ad3b9 100644 --- a/examples/chatbox_nodb.py +++ b/examples/chatbox_nodb.py @@ -1,7 +1,6 @@ -import os import sys -import re import telepot +from telepot.delegate import per_chat_id_in, call, create_open """ $ python3.2 chatbox_nodb.py @@ -50,13 +49,10 @@ def unread_per_chat(self): return [(k,len(v)) for k,v in self._db.items()] -# See `ChatBox` constructor to see how this class is used. -# Being a subclass of `ChatHandler` is useful, for it gives you many facilities, -# e.g. listener, sender, etc +# Accept commands from owner. Give him unread messages. class OwnerHandler(telepot.helper.ChatHandler): - def __init__(self, seed_tuple, store): - super(OwnerHandler, self).__init__(*seed_tuple) - self.listener.timeout = 20 # timeout after 20 seconds of inactivity + def __init__(self, seed_tuple, timeout, store): + super(OwnerHandler, self).__init__(seed_tuple, timeout) self._store = store def _read_messages(self, messages): @@ -64,7 +60,7 @@ def _read_messages(self, messages): # assume all messages are text self.sender.sendMessage(msg['text']) - def _handle(self, msg): + def on_message(self, msg): content_type, chat_type, chat_id = telepot.glance2(msg) if content_type != 'text': @@ -104,36 +100,39 @@ def _handle(self, msg): else: self.sender.sendMessage("I don't understand") - def run(self): - self._handle(self.initial_message) - while 1: - # Use listener to wait for next message from owner - msg = self.listener.wait(chat__id=self.chat_id) - self._handle(msg) +class MessageSaver(telepot.helper.Monitor): + def __init__(self, seed_tuple, store, exclude): + # The `capture` criteria means to capture all messages. + super(MessageSaver, self).__init__(seed_tuple, capture=[{'_': lambda msg: True}]) + self._store = store + self._exclude = exclude + # Store every message, except those whose sender is in the exclude list, or non-text messages. + def on_message(self, msg): + content_type, chat_type, chat_id = telepot.glance2(msg) -# See `ChatBox` constructor to see how this class is used. -class NewcomerHandler(telepot.helper.ChatHandler): - def __init__(self, seed_tuple): - super(NewcomerHandler, self).__init__(*seed_tuple) + if chat_id in self._exclude: + print('Chat id %d is excluded.' % chat_id) + return - # The action is very simple: just send a welcome message. - def run(self): - print('NewcomerHandler: sending welcome') - self.sender.sendMessage('Hello!') + if content_type != 'text': + print('Content type %s is ignored.' % content_type) + return + + print('Storing message: %s' % msg) + self._store.put(msg) import threading -# See `ChatBox` constructor to see how this class, combined with `custom_thread` -# function below, is used to wrap around a `NewcomerHandler` object to provide -# a custom delegate implementation. class CustomThread(threading.Thread): def start(self): print('CustomThread starting ...') super(CustomThread, self).start() +# Note how this function wraps around the `call()` function below to implement +# a custom thread for delegation. def custom_thread(func): def f(seed_tuple): target = func(seed_tuple) @@ -148,8 +147,6 @@ def f(seed_tuple): return f -from telepot.delegate import per_chat_id_in, call, create_run - class ChatBox(telepot.DelegatorBot): def __init__(self, token, owner_id): self._owner_id = owner_id @@ -157,47 +154,33 @@ def __init__(self, token, owner_id): self._store = UnreadStore() super(ChatBox, self).__init__(token, [ - # For each owner, create an OwnerHandler and spawn a thread - # around its `run()` method. - (per_chat_id_in([owner_id]), create_run(OwnerHandler, self._store)), - # Note how extra arguments are supplied to OwnerHandler's constructor. - - # For non-owners, just store the message. Since this is very simple, - # I choose to do it in a method - no objects required. - (self._others, call(self._store_message, self._store)), - # Note how extra arguments are supplied to the method. - - # For non-owners who have never been seen, - # create a NewcomerHandler and spawn a *custom* thread - # around its `run()` method. - (self._newcomer, custom_thread(create_run(NewcomerHandler))), - - # Note that the 2nd and 3rd condition are not exclusive. For a newcomer, - # both are triggered - that is, a welcome message is sent AND the message - # is stored. - ]) - - def _others(self, msg): - chat_id = msg['chat']['id'] - return [] if chat_id != self._owner_id else None - # [] non-hashable --> delegates are independent, no need to associate with a seed. + # Here is a delegate to specially handle owner commands. + (per_chat_id_in([owner_id]), create_open(OwnerHandler, 20, self._store)), + + # Seed is always the same, meaning only one MessageSaver is ever spawned for entire application. + (lambda msg: 1, create_open(MessageSaver, self._store, exclude=[owner_id])), - def _newcomer(self, msg): + # For senders never seen before, send him a welcome message. + (self._is_newcomer, custom_thread(call(self._send_welcome))), + ]) + + # seed-calculating function: use returned value to indicate whether to spawn a delegate + def _is_newcomer(self, msg): chat_id = msg['chat']['id'] - if chat_id == self._owner_id: - return None + if chat_id == self._owner_id: # Sender is owner + return None # No delegate spawned - if chat_id in self._seen: - return None + if chat_id in self._seen: # Sender has been seen before + return None # No delegate spawned self._seen.add(chat_id) - return [] - # [] non-hashable --> delegates are independent, no need to associate with a seed. + return [] # non-hashable ==> delegates are independent, no seed association is made. - def _store_message(self, seed_tuple, store): - msg = seed_tuple[1] - print('Storing message: %s' % msg) - store.put(msg) + def _send_welcome(self, seed_tuple): + chat_id = seed_tuple[1]['chat']['id'] + + print('Sending welcome ...') + self.sendMessage(chat_id, 'Hello!') TOKEN = sys.argv[1] diff --git a/examples/chatboxa_nodb.py b/examples/chatboxa_nodb.py index 7a7caf4..1aab4aa 100644 --- a/examples/chatboxa_nodb.py +++ b/examples/chatboxa_nodb.py @@ -1,10 +1,8 @@ -import os import sys -import re -import traceback import asyncio import telepot -import telepot.async +from telepot.delegate import per_chat_id_in +from telepot.async.delegate import call, create_open """ Python3.4.3 or newer @@ -54,15 +52,10 @@ def unread_per_chat(self): return [(k,len(v)) for k,v in self._db.items()] -# See `ChatBox` constructor to see how this class is used. -# Being a subclass of `ChatHandler` is useful, for it gives you many facilities, -# e.g. listener, sender, etc +# Accept commands from owner. Give him unread messages. class OwnerHandler(telepot.helper.ChatHandler): - WAIT_TIMEOUT = 20 - - def __init__(self, seed_tuple, store): - super(OwnerHandler, self).__init__(*seed_tuple) - self.listener.timeout = 20 # timeout after 20 seconds of inactivity + def __init__(self, seed_tuple, timeout, store): + super(OwnerHandler, self).__init__(seed_tuple, timeout) self._store = store @asyncio.coroutine @@ -72,9 +65,9 @@ def _read_messages(self, messages): yield from self.sender.sendMessage(msg['text']) @asyncio.coroutine - def _handle(self, msg): + def on_message(self, msg): content_type, chat_type, chat_id = telepot.glance2(msg) - + if content_type != 'text': yield from self.sender.sendMessage("I don't understand") return @@ -112,34 +105,29 @@ def _handle(self, msg): else: yield from self.sender.sendMessage("I don't understand") - @asyncio.coroutine - def run(self): - yield from self._handle(self.initial_message) - try: - while 1: - # Use listener to wait for next message from owner - msg = yield from asyncio.wait_for(self.listener.wait(chat__id=self.chat_id), self.WAIT_TIMEOUT) - yield from self._handle(msg) - except: - traceback.print_exc() - # display exceptions immediately +class MessageSaver(telepot.helper.Monitor): + def __init__(self, seed_tuple, store, exclude): + # The `capture` criteria means to capture all messages. + super(MessageSaver, self).__init__(seed_tuple, capture=[{'_': lambda msg: True}]) + self._store = store + self._exclude = exclude + # Store every message, except those whose sender is in the exclude list, or non-text messages. + def on_message(self, msg): + content_type, chat_type, chat_id = telepot.glance2(msg) -# See `ChatBox` constructor to see how this class is used. -class NewcomerHandler(telepot.helper.ChatHandler): - def __init__(self, seed_tuple): - super(NewcomerHandler, self).__init__(*seed_tuple) + if chat_id in self._exclude: + print('Chat id %d is excluded.' % chat_id) + return - # The action is very simple: just send a welcome message. - @asyncio.coroutine - def run(self): - print('NewcomerHandler: sending welcome') - yield from self.sender.sendMessage('Hello!') + if content_type != 'text': + print('Content type %s is ignored.' % content_type) + return + print('Storing message: %s' % msg) + self._store.put(msg) -from telepot.delegate import per_chat_id_in -from telepot.async.delegate import call, create_run class ChatBox(telepot.async.DelegatorBot): def __init__(self, token, owner_id): @@ -148,49 +136,34 @@ def __init__(self, token, owner_id): self._store = UnreadStore() super(ChatBox, self).__init__(token, [ - # For each owner, create an OwnerHandler and obtain a coroutine object - # from its `run()` method. - (per_chat_id_in([owner_id]), create_run(OwnerHandler, self._store)), - # Note how extra arguments are supplied to OwnerHandler's constructor. - - # For non-owners, just store the message. Since this is very simple, - # I choose to do it in a method - no objects required. - (self._others, call(self._store_message, self._store)), - # Note how extra arguments are supplied to the method. - - # For non-owners who have never been seen, - # create a NewcomerHandler and obtain a coroutine object - # from its `run()` method. - (self._newcomer, create_run(NewcomerHandler)), - - # Note that the 2nd and 3rd condition are not exclusive. For a newcomer, - # both are triggered - that is, a welcome message is sent AND the message - # is stored. - ]) - - def _others(self, msg): - chat_id = msg['chat']['id'] - return [] if chat_id != self._owner_id else None - # [] non-hashable --> delegates are independent, no need to associate with a seed. + # Here is a delegate to specially handle owner commands. + (per_chat_id_in([owner_id]), create_open(OwnerHandler, 20, self._store)), - def _newcomer(self, msg): + # Seed is always the same, meaning only one MessageSaver is ever spawned for entire application. + (lambda msg: 1, create_open(MessageSaver, self._store, exclude=[owner_id])), + + # For senders never seen before, send him a welcome message. + (self._is_newcomer, call(self._send_welcome)), + ]) + + # seed-calculating function: use returned value to indicate whether to spawn a delegate + def _is_newcomer(self, msg): chat_id = msg['chat']['id'] - if chat_id == self._owner_id: - return None + if chat_id == self._owner_id: # Sender is owner + return None # No delegate spawned - if chat_id in self._seen: - return None + if chat_id in self._seen: # Sender has been seen before + return None # No delegate spawned self._seen.add(chat_id) - return [] - # [] non-hashable --> delegates are independent, no need to associate with a seed. + return [] # non-hashable ==> delegates are independent, no seed association is made. - # Must be a coroutine because it is used as a delegate @asyncio.coroutine - def _store_message(self, seed_tuple, store): - msg = seed_tuple[1] - print('Storing message: %s' % msg) - store.put(msg) + def _send_welcome(self, seed_tuple): + chat_id = seed_tuple[1]['chat']['id'] + + print('Sending welcome ...') + yield from self.sendMessage(chat_id, 'Hello!') TOKEN = sys.argv[1] diff --git a/examples/counter.py b/examples/counter.py index 26c7e6e..010b21a 100644 --- a/examples/counter.py +++ b/examples/counter.py @@ -1,37 +1,26 @@ import sys import telepot -from telepot.delegate import create_run +from telepot.delegate import per_chat_id, create_open """ -$ python2.7 count.py +$ python2.7 counter.py Count number of messages. Start over if silent for 10 seconds. """ -# Being a subclass of ChatHandler is very useful, for it gives you: -# listener, sender, etc - a lot of facilities. class MessageCounter(telepot.helper.ChatHandler): - def __init__(self, seed_tuple): - super(MessageCounter, self).__init__(*seed_tuple) - self.listener.timeout = 10 - # conversation ends if no more messages after 10 seconds + def __init__(self, seed_tuple, timeout): + super(MessageCounter, self).__init__(seed_tuple, timeout) + self._count = 0 - def run(self): - count = 1 - self.sender.sendMessage(count) - # `sender` lets you send messages without mentioning chat_id every time - - while 1: - # wait for user's next message - msg = self.listener.wait(chat__id=self.chat_id) - count += 1 - self.sender.sendMessage(count) + 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, [ - # For each chat id, create a MessageCounter and delegate to its `run()` method - (lambda msg: msg['chat']['id'], create_run(MessageCounter)), + (per_chat_id(), create_open(MessageCounter, timeout=10)), ]) bot.notifyOnMessage(run_forever=True) diff --git a/examples/guess.py b/examples/guess.py index 1524d7c..2bdae94 100644 --- a/examples/guess.py +++ b/examples/guess.py @@ -1,26 +1,24 @@ import sys import random import telepot -import telepot.helper +from telepot.delegate import per_chat_id, create_open """ $ python2.7 guess.py Guess a number: -1. The bot randomly picks an integer between 0-100. -2. You make a guess. -3. The bot tells you to go higher or lower. -4. Repeat step 2 and 3, until guess is correct. +1. Send the bot anything to start a game. +2. The bot randomly picks an integer between 0-99. +3. You make a guess. +4. The bot tells you to go higher or lower. +5. Repeat step 3 and 4, until guess is correct. """ -# Being a subclass of ChatHandler is useful, for it gives you many facilities, -# e.g. listener, sender, etc. class Player(telepot.helper.ChatHandler): - def __init__(self, seed_tuple): - super(Player, self).__init__(*seed_tuple) - self.listener.timeout = 10 - # Raise exception after 10 seconds of no reply, ending the thread. + def __init__(self, seed_tuple, timeout): + super(Player, self).__init__(seed_tuple, timeout) + self._answer = random.randint(0,99) def _hint(self, answer, guess): if answer > guess: @@ -28,44 +26,40 @@ def _hint(self, answer, guess): else: return 'smaller' - def run(self): - # pick a number - answer = random.randint(0,100) - + def open(self, initial_msg, seed): self.sender.sendMessage('Guess my number') + return True # prevent on_message() from being called on the initial message + + def on_message(self, msg): + content_type, chat_type, chat_id = telepot.glance2(msg) + + if content_type != 'text': + self.sender.sendMessage('Give me a number, please.') + return + + try: + guess = int(msg['text']) + except ValueError: + self.sender.sendMessage('Give me a number, please.') + return + + # check the guess against the answer ... + if guess != self._answer: + # give a descriptive hint + hint = self._hint(self._answer, guess) + self.sender.sendMessage(hint) + else: + self.sender.sendMessage('Correct!') + self.close() - while 1: - # wait for user's guess - msg = self.listener.wait(chat__id=self.chat_id) - - content_type, chat_type, chat_id = telepot.glance2(msg) - - if content_type != 'text': - self.sender.sendMessage('Give me a number, please.') - continue - - try: - guess = int(msg['text']) - except ValueError: - self.sender.sendMessage('Give me a number, please.') - continue - - # check the guess against the answer ... - if guess != answer: - # give a descriptive hint - hint = self._hint(answer, guess) - self.sender.sendMessage(hint) - else: - self.sender.sendMessage('Correct!') - return + def on_close(self, exception): + if isinstance(exception, telepot.helper.WaitTooLong): + self.sender.sendMessage('Game expired. The answer is %d' % self._answer) TOKEN = sys.argv[1] -from telepot.delegate import per_chat_id, create_run - bot = telepot.DelegatorBot(TOKEN, [ - # For each chat_id, create a Player and delegate to its `run` method. - (per_chat_id(), create_run(Player)), - ]) + (per_chat_id(), create_open(Player, timeout=10)), +]) bot.notifyOnMessage(run_forever=True) diff --git a/examples/guessa.py b/examples/guessa.py index 8fe19df..eed8365 100644 --- a/examples/guessa.py +++ b/examples/guessa.py @@ -1,29 +1,26 @@ import sys import asyncio import random -import traceback import telepot -import telepot.helper -import telepot.async +from telepot.delegate import per_chat_id +from telepot.async.delegate import create_open """ $ python3.4 guessa.py Guess a number: -1. The bot randomly picks an integer between 0-100. -2. You make a guess. -3. The bot tells you to go higher or lower. -4. Repeat step 2 and 3, until guess is correct. +1. Send the bot anything to start a game. +2. The bot randomly picks an integer between 0-99. +3. You make a guess. +4. The bot tells you to go higher or lower. +5. Repeat step 3 and 4, until guess is correct. """ -# Being a subclass of ChatHandler is useful, for it gives you many facilities, -# e.g. listener, sender, etc. class Player(telepot.helper.ChatHandler): - WAIT_TIMEOUT = 10 - - def __init__(self, seed_tuple): - super(Player, self).__init__(*seed_tuple) + def __init__(self, seed_tuple, timeout): + super(Player, self).__init__(seed_tuple, timeout) + self._answer = random.randint(0,99) def _hint(self, answer, guess): if answer > guess: @@ -32,51 +29,44 @@ def _hint(self, answer, guess): return 'smaller' @asyncio.coroutine - def run(self): - try: - # pick a number - answer = random.randint(0,100) + def open(self, initial_msg, seed): + yield from self.sender.sendMessage('Guess my number') + return True # prevent on_message() from being called on the initial message - yield from self.sender.sendMessage('Guess my number') - - while 1: - # wait for user's guess - msg = yield from asyncio.wait_for(self.listener.wait(chat__id=self.chat_id), self.WAIT_TIMEOUT) - - content_type, chat_type, chat_id = telepot.glance2(msg) + @asyncio.coroutine + def on_message(self, msg): + content_type, chat_type, chat_id = telepot.glance2(msg) - if content_type != 'text': - yield from self.sender.sendMessage('Give me a number, please.') - continue + if content_type != 'text': + yield from self.sender.sendMessage('Give me a number, please.') + return - try: - guess = int(msg['text']) - except ValueError: - yield from self.sender.sendMessage('Give me a number, please.') - continue + try: + guess = int(msg['text']) + except ValueError: + yield from self.sender.sendMessage('Give me a number, please.') + return + + # check the guess against the answer ... + if guess != self._answer: + # give a descriptive hint + hint = self._hint(self._answer, guess) + yield from self.sender.sendMessage(hint) + else: + yield from self.sender.sendMessage('Correct!') + self.close() - # check the guess against the answer ... - if guess != answer: - # give a descriptive hint - hint = self._hint(answer, guess) - yield from self.sender.sendMessage(hint) - else: - yield from self.sender.sendMessage('Correct!') - return - except: - traceback.print_exc() - # display exceptions immediately + @asyncio.coroutine + def on_close(self, exception): + if isinstance(exception, telepot.helper.WaitTooLong): + yield from self.sender.sendMessage('Game expired. The answer is %d' % self._answer) TOKEN = sys.argv[1] -from telepot.delegate import per_chat_id -from telepot.async.delegate import create_run - bot = telepot.async.DelegatorBot(TOKEN, [ - # For each chat_id, create a Player and delegate to its `run()` method. - (per_chat_id(), create_run(Player)), - ]) + (per_chat_id(), create_open(Player, timeout=10)), +]) loop = asyncio.get_event_loop() loop.create_task(bot.messageLoop())