mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-03-27 00:48:54 +01:00
Most examples use the same error handler, that error handler logs update.to_dict but doesn't log error traceback. Hiding error traceback is quite bad, removing the error handler entirely causes PTB to use default error logging which does include error traceback.
366 lines
12 KiB
Python
366 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
|
|
|
|
|
|
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)
|
|
# Because the states of the third level conversation map to the ones of the econd level
|
|
# conversation, we need to make sure the top level conversation can also handle them
|
|
selection_handlers = [
|
|
add_member_conv,
|
|
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
|
CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
|
|
CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
|
|
]
|
|
conv_handler = ConversationHandler(
|
|
entry_points=[CommandHandler('start', start)],
|
|
|
|
states={
|
|
SHOWING: [CallbackQueryHandler(start, pattern='^' + str(END) + '$')],
|
|
SELECTING_ACTION: selection_handlers,
|
|
SELECTING_LEVEL: selection_handlers,
|
|
DESCRIBING_SELF: [description_conv],
|
|
STOPPING: [CommandHandler('start', start)],
|
|
},
|
|
|
|
fallbacks=[CommandHandler('stop', stop)],
|
|
)
|
|
|
|
dp.add_handler(conv_handler)
|
|
|
|
# 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()
|