# -*- coding: utf-8 -*- from telegram.ext import Updater # python-telegram-bot, Python- from telegram.ext import CommandHandler # - telegram ¯\_(ツ)_/¯ def start(bot, update): # update: https://core.telegram.org/bots/api#update bot.sendMessage(chat_id=update.message.chat_id, text=".") updater = Updater(token='TOKEN') # , ! start_handler = CommandHandler('start', start) # # /start updater.dispatcher.add_handler(start_handler) # updater.start_polling() # !
/start
command) and is meaningfully silent during any subsequent actions on your part. from telegram.ext import Filters, MessageHandler def handle_text(bot, update): # ... def handle_command(bot, update): # ... # MessageHandler -- , text_handler = MessageHandler(Filters.text, self.handle_text) command_handler = MessageHandler(Filters.command, self.handle_command) # updater.dispatcher.add_handler(text_handler) # , updater.dispatcher.add_handler(command_handler) # ()
yield
.yield
? Well, we all know that this is such a thing to write generators, that is, such lazy and potentially endless lists: def fibonacci(): a, b = 1, 1 while True: yield a a, b = b, a + b f = fibonacci()
f
is such a magic box; it is necessary next(f)
, and we get the next Fibonacci number, but it is worth list(f)
, as we go into an infinite loop, which most likely ends in a tragic death of the system from a lack of RAM.itertools
, offering generators for every taste and color. But we have something else.yield
are the abilities ... to return values and throw exceptions! Yes, yes, if we write: f.throw(Exception)
yield
.f.send(something)
will force the yield
construct to return a value, and then immediately return next(f)
. It is enough to equate the yield
variable in order to catch the passed value: def doubler(start): while True: start = yield (start, start) d = doubler(42) print(next(d)) # (42, 42) print(next(d)) # (None, None), - ! print(d.send(43)) # (43, 43) -- yield 43, yield
yield from
construct: instead of def concatenate(iterable1, iterable2): for item in iterable1: yield item for item in iterable2: yield item
def concatenate(iterable1, iterable2): yield from iterable1 yield from iterable2
yield from
allows us to only save lines of code on cycles. The fact is that it also takes care of send
and throw
— when called, they will interact not with the function concatenate
, but with one of two generators to which it transfers control. (If it turns out not to be generators ... well, oops.)yield from
also knows how to return a value: for this, the right to non-trivial (that is, returning something, and not just terminating execution) return
generator functions: def despaired_person(): yield None yield None yield None return "I'm tired of my uselessness" def despair_expresser(): result = yield from despaired_person() print(result) print(list(f())) # # I'm tired of my uselessness # [None, None, None]
yield
will issue a message to the outside, which must be sent to the user, and return the answer to it (as soon as it appears). Let's write a simple class that knows how to do it. import collections from telegram.ext import Filters from telegram.ext import MessageHandler from telegram.ext import Updater class DialogBot(object): def __init__(self, token, generator): self.updater = Updater(token=token) # handler = MessageHandler(Filters.text | Filters.command, self.handle_message) self.updater.dispatcher.add_handler(handler) # self.handlers = collections.defaultdict(generator) # "id -> " def start(self): self.updater.start_polling() def handle_message(self, bot, update): print("Received", update.message) chat_id = update.message.chat_id if update.message.text == "/start": # /start, -- # , self.handlers.pop(chat_id, None) if chat_id in self.handlers: # , .send(), # try: answer = self.handlers[chat_id].send(update.message) except StopIteration: # -- , del self.handlers[chat_id] # ( , , ) return self.handle_message(bot, update) else: # . defaultdict # , .next() # (.send() yield) answer = next(self.handlers[chat_id]) # print("Answer: %r" % answer) bot.sendMessage(chat_id=chat_id, text=answer)
def dialog(): answer = yield "! , ?" # , # , name = answer.text.rstrip(".!").split()[0].capitalize() likes_python = yield from ask_yes_or_no(" , %s. ?" % name) if likes_python: answer = yield from discuss_good_python(name) else: answer = yield from discuss_bad_python(name) def ask_yes_or_no(question): """ , «» «». : bool """ answer = yield question while not ("" in answer.text.lower() or "" in answer.text.lower()): answer = yield " ?" return "" in answer.text.lower() def discuss_good_python(name): answer = yield " , %s, ! ?" % name likes_article = yield from ask_yes_or_no(". , , ? ?") if likes_article: answer = yield "!" else: answer = yield "." return answer def discuss_bad_python(name): answer = yield "--. %s, ! ?" % name likes_article = yield from ask_yes_or_no( " . " " , , ?") if likes_article: answer = yield " ." else: answer = yield " «»? «, » «, »?" answer = yield ", ." return answer if __name__ == "__main__": dialog_bot = DialogBot(sys.argv[1], dialog) dialog_bot.start()
Bot.sendMessage
function: def sendMessage(self, chat_id, text, parse_mode=None, disable_web_page_preview=None, disable_notification=False, reply_to_message_id=None, reply_markup=None, timeout=None, **kwargs): """Use this method to send text messages. Args: chat_id (str): ... text (str): ... parse_mode (Optional[str]): Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message. disable_web_page_preview (Optional[bool]): ... disable_notification (Optional[bool]): ... reply_to_message_id (Optional[int]): ... reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to hide reply keyboard or to force a reply from the user. timeout (Optional[float]): ... ... """
parse_mode="HTML"
or parse_mode="Markdown"
. One could just add this to our challenge, but let's make it a bit more deadly: add special objects that need to be raised to trigger the use of markup: class Message(object): def __init__(self, text, **options): self.text = text self.options = options class Markdown(Message): def __init__(self, text, **options): super(Markup, self).__init__(text, parse_mode="Markdown", **options) class HTML(Message): def __init__(self, text, **options): super(HTML, self).__init__(text, parse_mode="HTML", **options)
def handle_message(self, bot, update): # ...... print("Answer: %r" % answer) self._send_answer(bot, chat_id, answer) def _send_answer(self, bot, chat_id, answer): if isinstance(answer, str): answer = Message(answer) bot.sendMessage(chat_id=chat_id, text=answer.text, **answer.options)
ask_yes_or_no()
: def ask_yes_or_no(question): answer = yield question while not ("" in answer.text.lower() or "" in answer.text.lower()): answer = yield HTML(" <b></b> <b></b>?") return "" in answer.text.lower()
reply_markup
key to reply_markup
; but let's try to simplify and abstract our code inside the generators as much as possible. Here the decision is easier. Let, for example, yield
returns not one object, but several at once; if among them there is a list or a list of lists with lines, for example: answer = yield ( " 1 9", [ ["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"], ] )
_send_answer()
then converted to something like this: def _send_answer(self, bot, chat_id, answer): print("Sending answer %r to %s" % (answer, chat_id)) if isinstance(answer, collections.abc.Iterable) and not isinstance(answer, str): # -- answer = list(map(self._convert_answer_part, answer)) else: # -- answer = [self._convert_answer_part(answer)] # , , # «» -- - current_message = None for part in answer: if isinstance(part, Message): if current_message is not None: # , # ( ) options = dict(current_message.options) options.setdefault("disable_notification", True) bot.sendMessage(chat_id=chat_id, text=current_message.text, **options) current_message = part if isinstance(part, ReplyMarkup): # , ! . # -- , . current_message.options["reply_markup"] = part # . if current_message is not None: bot.sendMessage(chat_id=chat_id, text=current_message.text, **current_message.options) def _convert_answer_part(self, answer_part): if isinstance(answer_part, str): return Message(answer_part) if isinstance(answer_part, collections.abc.Iterable): # ? answer_part = list(answer_part) if isinstance(answer_part[0], str): # ! . # , -- . return ReplyKeyboardMarkup([answer_part], one_time_keyboard=True) elif isinstance(answer_part[0], collections.abc.Iterable): # ? if isinstance(answer_part[0][0], str): # ! return ReplyKeyboardMarkup(map(list, answer_part), one_time_keyboard=True) return answer_part
ask_yes_or_no()
and discuss_bad_python()
: def ask_yes_or_no(question): """ , «» «». : bool """ answer = yield (question, [".", "."]) while not ("" in answer.text.lower() or "" in answer.text.lower()): answer = yield HTML(" <b></b> <b></b>?") return "" in answer.text.lower() def discuss_bad_python(name): answer = yield "--. %s, ! ?" % name likes_article = yield from ask_yes_or_no( " . " " , , ?") if likes_article: answer = yield " ." else: answer = yield ( " «»? «, » «, »?", [", !", ", !"] ) answer = yield ", ." return answer
habrahabr-316666
). I will not give the link to the bot and I will not keep it alive, of course, in the near future, otherwise the habraeffect will cover it with my computer. Successes in creating their interactive bots 😉Source: https://habr.com/ru/post/316666/
All Articles