Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/source/telegram.ext.basepersistence.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
telegram.ext.BasePersistence
============================

.. autoclass:: telegram.ext.BasePersistence
:members:
:show-inheritance:
6 changes: 6 additions & 0 deletions docs/source/telegram.ext.dictpersistence.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
telegram.ext.DictPersistence
============================

.. autoclass:: telegram.ext.DictPersistence
:members:
:show-inheritance:
6 changes: 6 additions & 0 deletions docs/source/telegram.ext.picklepersistence.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
telegram.ext.PicklePersistence
==============================

.. autoclass:: telegram.ext.PicklePersistence
:members:
:show-inheritance:
9 changes: 9 additions & 0 deletions docs/source/telegram.ext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ Handlers
telegram.ext.stringcommandhandler
telegram.ext.stringregexhandler
telegram.ext.typehandler

Persistence
-----------

.. toctree::

telegram.ext.basepersistence
telegram.ext.picklepersistence
telegram.ext.dictpersistence
3 changes: 3 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ A basic example of an [inline bot](https://core.telegram.org/bots/inline). Don't
### [`paymentbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/paymentbot.py)
A basic example of a bot that can accept payments. Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather).

### [`persistentconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/persistentconversationbot.py)
A basic example of a bot store conversation state and user_data over multiple restarts.

## Pure API
The [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py) example uses only the pure, "bare-metal" API wrapper.
170 changes: 170 additions & 0 deletions examples/persistentconversationbot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to reply to Telegram messages
# This program is dedicated to the public domain under the CC0 license.
"""
This Bot uses the Updater class to handle the bot.

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 ConversationHandler.
Send /start to initiate the conversation.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""

from telegram import ReplyKeyboardMarkup
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler,
ConversationHandler, PicklePersistence)

import logging

# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)

logger = logging.getLogger(__name__)

CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)

reply_keyboard = [['Age', 'Favourite colour'],
['Number of siblings', 'Something else...'],
['Done']]
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)


def facts_to_str(user_data):
facts = list()

for key, value in user_data.items():
facts.append('{} - {}'.format(key, value))

return "\n".join(facts).join(['\n', '\n'])


def start(bot, update, user_data):
reply_text = "Hi! My name is Doctor Botter."
if user_data:
reply_text += " You already told me your {}. Why don't you tell me something more " \
"about yourself? Or change enything I " \
"already know.".format(", ".join(user_data.keys()))
else:
reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \
"something about yourself?"
update.message.reply_text(reply_text, reply_markup=markup)

return CHOOSING


def regular_choice(bot, update, user_data):
text = update.message.text
user_data['choice'] = text
if user_data.get(text):
reply_text = 'Your {}, I already know the following ' \
'about that: {}'.format(text.lower(), user_data[text.lower()])
else:
reply_text = 'Your {}? Yes, I would love to hear about that!'.format(text.lower())
update.message.reply_text(reply_text)

return TYPING_REPLY


def custom_choice(bot, update):
update.message.reply_text('Alright, please send me the category first, '
'for example "Most impressive skill"')

return TYPING_CHOICE


def received_information(bot, update, user_data):
text = update.message.text
category = user_data['choice']
user_data[category] = text.lower()
del user_data['choice']

update.message.reply_text("Neat! Just so you know, this is what you already told me:"
"{}"
"You can tell me more, or change your opinion on "
"something.".format(facts_to_str(user_data)), reply_markup=markup)

return CHOOSING


def show_data(bot, update, user_data):
update.message.reply_text("This is what you already told me:"
"{}".format(facts_to_str(user_data)))


def done(bot, update, user_data):
if 'choice' in user_data:
del user_data['choice']

update.message.reply_text("I learned these facts about you:"
"{}"
"Until next time!".format(facts_to_str(user_data)))
return ConversationHandler.END


def error(bot, update, error):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)


def main():
# Create the Updater and pass it your bot's token.
pp = PicklePersistence(filename='conversationbot')
updater = Updater("TOKEN", persistence=pp)

# Get the dispatcher to register handlers
dp = updater.dispatcher

# Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start, pass_user_data=True)],

states={
CHOOSING: [RegexHandler('^(Age|Favourite colour|Number of siblings)$',
regular_choice,
pass_user_data=True),
RegexHandler('^Something else...$',
custom_choice),
],

TYPING_CHOICE: [MessageHandler(Filters.text,
regular_choice,
pass_user_data=True),
],

TYPING_REPLY: [MessageHandler(Filters.text,
received_information,
pass_user_data=True),
],
},

fallbacks=[RegexHandler('^Done$', done, pass_user_data=True)],
name="my_conversation",
persistent=True
)

dp.add_handler(conv_handler)

show_data_handler = CommandHandler('show_data', show_data, pass_user_data=True)
dp.add_handler(show_data_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()
6 changes: 5 additions & 1 deletion telegram/ext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Extensions over the Telegram Bot API to facilitate bot making"""

from .basepersistence import BasePersistence
from .picklepersistence import PicklePersistence
from .dictpersistence import DictPersistence
from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async
from .jobqueue import JobQueue, Job
from .updater import Updater
Expand All @@ -43,4 +46,5 @@
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
'DispatcherHandlerStop', 'run_async')
'DispatcherHandlerStop', 'run_async', 'BasePersistence', 'PicklePersistence',
'DictPersistence')
123 changes: 123 additions & 0 deletions telegram/ext/basepersistence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the BasePersistence class."""


class BasePersistence(object):
"""Interface class for adding persistence to your bot.
Subclass this object for different implementations of a persistent bot.

All relevant methods must be overwritten. This means:

* If :attr:`store_chat_data` is ``True`` you must overwrite :meth:`get_chat_data` and
:meth:`update_chat_data`.
* If :attr:`store_user_data` is ``True`` you must overwrite :meth:`get_user_data` and
:meth:`update_user_data`.
* If you want to store conversation data with :class:`telegram.ext.ConversationHandler`, you
must overwrite :meth:`get_conversations` and :meth:`update_conversation`.
* :meth:`flush` will be called when the bot is shutdown.

Attributes:
store_user_data (:obj:`bool`): Optional, Whether user_data should be saved by this
persistence class.
store_chat_data (:obj:`bool`): Optional. Whether chat_data should be saved by this
persistence class.

Args:
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this
persistence class. Default is ``True`` .
"""

def __init__(self, store_user_data=True, store_chat_data=True):
self.store_user_data = store_user_data
self.store_chat_data = store_chat_data

def get_user_data(self):
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the user_data if stored, or an empty
``defaultdict(dict)``.

Returns:
:obj:`defaultdict`: The restored user data.
"""
raise NotImplementedError

def get_chat_data(self):
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the chat_data if stored, or an empty
``defaultdict(dict)``.

Returns:
:obj:`defaultdict`: The restored chat data.
"""
raise NotImplementedError

def get_conversations(self, name):
""""Will be called by :class:`telegram.ext.Dispatcher` when a
:class:`telegram.ext.ConversationHandler` is added if
:attr:`telegram.ext.ConversationHandler.persistent` is ``True``.
It should return the conversations for the handler with `name` or an empty ``dict``

Args:
name (:obj:`str`): The handlers name.

Returns:
:obj:`dict`: The restored conversations for the handler.
"""
raise NotImplementedError

def update_conversation(self, name, key, new_state):
"""Will be called when a :attr:`telegram.ext.ConversationHandler.update_state`
is called. this allows the storeage of the new state in the persistence.

Args:
name (:obj:`str`): The handlers name.
key (:obj:`tuple`): The key the state is changed for.
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
"""
raise NotImplementedError

def update_user_data(self, user_id, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.

Args:
user_id (:obj:`int`): The user the data might have been changed for.
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data`[user_id].
"""
raise NotImplementedError

def update_chat_data(self, chat_id, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.

Args:
chat_id (:obj:`int`): The chat the data might have been changed for.
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data`[user_id].
"""
raise NotImplementedError

def flush(self):
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
persistence a chance to finish up saving or close a database connection gracefully. If this
is not of any importance just pass will be sufficient.
"""
pass
Loading