mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-11-30 04:02:49 +01:00
374 lines
12 KiB
Python
374 lines
12 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
# This program is dedicated to the public domain under the CC0 license.
|
|
|
|
"""
|
|
First, a few callback functions are defined. Then, those functions are passed to
|
|
the Dispatcher and registered at their respective places.
|
|
Then, the bot is started and runs until we press Ctrl-C on the command line.
|
|
|
|
Usage:
|
|
Example of a bot-user conversation using nested ConversationHandlers.
|
|
Send /start to initiate the conversation.
|
|
Press Ctrl-C on the command line or send a signal to the process to stop the
|
|
bot.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from telegram import (InlineKeyboardMarkup, InlineKeyboardButton)
|
|
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
|
ConversationHandler, CallbackQueryHandler)
|
|
|
|
# Enable logging
|
|
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
level=logging.INFO)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# State definitions for top level conversation
|
|
SELECTING_ACTION, ADDING_MEMBER, ADDING_SELF, DESCRIBING_SELF = map(chr, range(4))
|
|
# State definitions for second level conversation
|
|
SELECTING_LEVEL, SELECTING_GENDER = map(chr, range(4, 6))
|
|
# State definitions for descriptions conversation
|
|
SELECTING_FEATURE, TYPING = map(chr, range(6, 8))
|
|
# Meta states
|
|
STOPPING, SHOWING = map(chr, range(8, 10))
|
|
# Shortcut for ConversationHandler.END
|
|
END = ConversationHandler.END
|
|
|
|
# Different constants for this example
|
|
(PARENTS, CHILDREN, SELF, GENDER, MALE, FEMALE, AGE, NAME, START_OVER, FEATURES,
|
|
CURRENT_FEATURE, CURRENT_LEVEL) = map(chr, range(10, 22))
|
|
|
|
|
|
# Helper
|
|
def _name_switcher(level):
|
|
if level == PARENTS:
|
|
return ('Father', 'Mother')
|
|
elif level == CHILDREN:
|
|
return ('Brother', 'Sister')
|
|
|
|
|
|
# Top level conversation callbacks
|
|
def start(update, context):
|
|
"""Select an action: Adding parent/child or show data."""
|
|
text = 'You may add a familiy member, yourself show the gathered data or end the ' \
|
|
'conversation. To abort, simply type /stop.'
|
|
buttons = [[
|
|
InlineKeyboardButton(text='Add family member', callback_data=str(ADDING_MEMBER)),
|
|
InlineKeyboardButton(text='Add yourself', callback_data=str(ADDING_SELF))
|
|
], [
|
|
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
|
InlineKeyboardButton(text='Done', callback_data=str(END))
|
|
]]
|
|
keyboard = InlineKeyboardMarkup(buttons)
|
|
|
|
# If we're starting over we don't need do send a new message
|
|
if context.user_data.get(START_OVER):
|
|
update.callback_query.answer()
|
|
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
|
else:
|
|
update.message.reply_text('Hi, I\'m FamiliyBot and here to help you gather information'
|
|
'about your family.')
|
|
update.message.reply_text(text=text, reply_markup=keyboard)
|
|
|
|
context.user_data[START_OVER] = False
|
|
return SELECTING_ACTION
|
|
|
|
|
|
def adding_self(update, context):
|
|
"""Add information about youself."""
|
|
context.user_data[CURRENT_LEVEL] = SELF
|
|
text = 'Okay, please tell me about yourself.'
|
|
button = InlineKeyboardButton(text='Add info', callback_data=str(MALE))
|
|
keyboard = InlineKeyboardMarkup.from_button(button)
|
|
|
|
update.callback_query.answer()
|
|
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
|
|
|
return DESCRIBING_SELF
|
|
|
|
|
|
def show_data(update, context):
|
|
"""Pretty print gathered data."""
|
|
def prettyprint(user_data, level):
|
|
people = user_data.get(level)
|
|
if not people:
|
|
return '\nNo information yet.'
|
|
|
|
text = ''
|
|
if level == SELF:
|
|
for person in user_data[level]:
|
|
text += '\nName: {0}, Age: {1}'.format(person.get(NAME, '-'), person.get(AGE, '-'))
|
|
else:
|
|
male, female = _name_switcher(level)
|
|
|
|
for person in user_data[level]:
|
|
gender = female if person[GENDER] == FEMALE else male
|
|
text += '\n{0}: Name: {1}, Age: {2}'.format(gender, person.get(NAME, '-'),
|
|
person.get(AGE, '-'))
|
|
return text
|
|
|
|
ud = context.user_data
|
|
text = 'Yourself:' + prettyprint(ud, SELF)
|
|
text += '\n\nParents:' + prettyprint(ud, PARENTS)
|
|
text += '\n\nChildren:' + prettyprint(ud, CHILDREN)
|
|
|
|
buttons = [[
|
|
InlineKeyboardButton(text='Back', callback_data=str(END))
|
|
]]
|
|
keyboard = InlineKeyboardMarkup(buttons)
|
|
|
|
update.callback_query.answer()
|
|
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
|
ud[START_OVER] = True
|
|
|
|
return SHOWING
|
|
|
|
|
|
def stop(update, context):
|
|
"""End Conversation by command."""
|
|
update.message.reply_text('Okay, bye.')
|
|
|
|
return END
|
|
|
|
|
|
def end(update, context):
|
|
"""End conversation from InlineKeyboardButton."""
|
|
update.callback_query.answer()
|
|
|
|
text = 'See you around!'
|
|
update.callback_query.edit_message_text(text=text)
|
|
|
|
return END
|
|
|
|
|
|
# Second level conversation callbacks
|
|
def select_level(update, context):
|
|
"""Choose to add a parent or a child."""
|
|
text = 'You may add a parent or a child. Also you can show the gathered data or go back.'
|
|
buttons = [[
|
|
InlineKeyboardButton(text='Add parent', callback_data=str(PARENTS)),
|
|
InlineKeyboardButton(text='Add child', callback_data=str(CHILDREN))
|
|
], [
|
|
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
|
InlineKeyboardButton(text='Back', callback_data=str(END))
|
|
]]
|
|
keyboard = InlineKeyboardMarkup(buttons)
|
|
|
|
update.callback_query.answer()
|
|
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
|
|
|
return SELECTING_LEVEL
|
|
|
|
|
|
def select_gender(update, context):
|
|
"""Choose to add mother or father."""
|
|
level = update.callback_query.data
|
|
context.user_data[CURRENT_LEVEL] = level
|
|
|
|
text = 'Please choose, whom to add.'
|
|
|
|
male, female = _name_switcher(level)
|
|
|
|
buttons = [[
|
|
InlineKeyboardButton(text='Add ' + male, callback_data=str(MALE)),
|
|
InlineKeyboardButton(text='Add ' + female, callback_data=str(FEMALE))
|
|
], [
|
|
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
|
InlineKeyboardButton(text='Back', callback_data=str(END))
|
|
]]
|
|
keyboard = InlineKeyboardMarkup(buttons)
|
|
|
|
update.callback_query.answer()
|
|
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
|
|
|
return SELECTING_GENDER
|
|
|
|
|
|
def end_second_level(update, context):
|
|
"""Return to top level conversation."""
|
|
context.user_data[START_OVER] = True
|
|
start(update, context)
|
|
|
|
return END
|
|
|
|
|
|
# Third level callbacks
|
|
def select_feature(update, context):
|
|
"""Select a feature to update for the person."""
|
|
buttons = [[
|
|
InlineKeyboardButton(text='Name', callback_data=str(NAME)),
|
|
InlineKeyboardButton(text='Age', callback_data=str(AGE)),
|
|
InlineKeyboardButton(text='Done', callback_data=str(END)),
|
|
]]
|
|
keyboard = InlineKeyboardMarkup(buttons)
|
|
|
|
# If we collect features for a new person, clear the cache and save the gender
|
|
if not context.user_data.get(START_OVER):
|
|
context.user_data[FEATURES] = {GENDER: update.callback_query.data}
|
|
text = 'Please select a feature to update.'
|
|
|
|
update.callback_query.answer()
|
|
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
|
# But after we do that, we need to send a new message
|
|
else:
|
|
text = 'Got it! Please select a feature to update.'
|
|
update.message.reply_text(text=text, reply_markup=keyboard)
|
|
|
|
context.user_data[START_OVER] = False
|
|
return SELECTING_FEATURE
|
|
|
|
|
|
def ask_for_input(update, context):
|
|
"""Prompt user to input data for selected feature."""
|
|
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
|
text = 'Okay, tell me.'
|
|
|
|
update.callback_query.answer()
|
|
update.callback_query.edit_message_text(text=text)
|
|
|
|
return TYPING
|
|
|
|
|
|
def save_input(update, context):
|
|
"""Save input for feature and return to feature selection."""
|
|
ud = context.user_data
|
|
ud[FEATURES][ud[CURRENT_FEATURE]] = update.message.text
|
|
|
|
ud[START_OVER] = True
|
|
|
|
return select_feature(update, context)
|
|
|
|
|
|
def end_describing(update, context):
|
|
"""End gathering of features and return to parent conversation."""
|
|
ud = context.user_data
|
|
level = ud[CURRENT_LEVEL]
|
|
if not ud.get(level):
|
|
ud[level] = []
|
|
ud[level].append(ud[FEATURES])
|
|
|
|
# Print upper level menu
|
|
if level == SELF:
|
|
ud[START_OVER] = True
|
|
start(update, context)
|
|
else:
|
|
select_level(update, context)
|
|
|
|
return END
|
|
|
|
|
|
def stop_nested(update, context):
|
|
"""Completely end conversation from within nested conversation."""
|
|
update.message.reply_text('Okay, bye.')
|
|
|
|
return STOPPING
|
|
|
|
|
|
# Error handler
|
|
def error(update, context):
|
|
"""Log Errors caused by Updates."""
|
|
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
|
|
|
|
|
def main():
|
|
# Create the Updater and pass it your bot's token.
|
|
# Make sure to set use_context=True to use the new context based callbacks
|
|
# Post version 12 this will no longer be necessary
|
|
updater = Updater("TOKEN", use_context=True)
|
|
|
|
# Get the dispatcher to register handlers
|
|
dp = updater.dispatcher
|
|
|
|
# Set up third level ConversationHandler (collecting features)
|
|
description_conv = ConversationHandler(
|
|
entry_points=[CallbackQueryHandler(select_feature,
|
|
pattern='^' + str(MALE) + '$|^' + str(FEMALE) + '$')],
|
|
|
|
states={
|
|
SELECTING_FEATURE: [CallbackQueryHandler(ask_for_input,
|
|
pattern='^(?!' + str(END) + ').*$')],
|
|
TYPING: [MessageHandler(Filters.text, save_input)],
|
|
},
|
|
|
|
fallbacks=[
|
|
CallbackQueryHandler(end_describing, pattern='^' + str(END) + '$'),
|
|
CommandHandler('stop', stop_nested)
|
|
],
|
|
|
|
map_to_parent={
|
|
# Return to second level menu
|
|
END: SELECTING_LEVEL,
|
|
# End conversation alltogether
|
|
STOPPING: STOPPING,
|
|
}
|
|
)
|
|
|
|
# Set up second level ConversationHandler (adding a person)
|
|
add_member_conv = ConversationHandler(
|
|
entry_points=[CallbackQueryHandler(select_level,
|
|
pattern='^' + str(ADDING_MEMBER) + '$')],
|
|
|
|
states={
|
|
SELECTING_LEVEL: [CallbackQueryHandler(select_gender,
|
|
pattern='^{0}$|^{1}$'.format(str(PARENTS),
|
|
str(CHILDREN)))],
|
|
SELECTING_GENDER: [description_conv]
|
|
},
|
|
|
|
fallbacks=[
|
|
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
|
CallbackQueryHandler(end_second_level, pattern='^' + str(END) + '$'),
|
|
CommandHandler('stop', stop_nested)
|
|
],
|
|
|
|
map_to_parent={
|
|
# After showing data return to top level menu
|
|
SHOWING: SHOWING,
|
|
# Return to top level menu
|
|
END: SELECTING_ACTION,
|
|
# End conversation alltogether
|
|
STOPPING: END,
|
|
}
|
|
)
|
|
|
|
# Set up top level ConversationHandler (selecting action)
|
|
conv_handler = ConversationHandler(
|
|
entry_points=[CommandHandler('start', start)],
|
|
|
|
states={
|
|
SHOWING: [CallbackQueryHandler(start, pattern='^' + str(END) + '$')],
|
|
SELECTING_ACTION: [
|
|
add_member_conv,
|
|
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
|
CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
|
|
CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
|
|
],
|
|
DESCRIBING_SELF: [description_conv],
|
|
},
|
|
|
|
fallbacks=[CommandHandler('stop', stop)],
|
|
)
|
|
# Because the states of the third level conversation map to the ones of the
|
|
# second level conversation, we need to be a bit hacky about that:
|
|
conv_handler.states[SELECTING_LEVEL] = conv_handler.states[SELECTING_ACTION]
|
|
conv_handler.states[STOPPING] = conv_handler.entry_points
|
|
|
|
dp.add_handler(conv_handler)
|
|
|
|
# log all errors
|
|
dp.add_error_handler(error)
|
|
|
|
# Start the Bot
|
|
updater.start_polling()
|
|
|
|
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
|
# SIGTERM or SIGABRT. This should be used most of the time, since
|
|
# start_polling() is non-blocking and will stop the bot gracefully.
|
|
updater.idle()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|