DEV Community

Cover image for Building An E-commerce Telegram Bot Using Python and Fauna.
Curious Paul
Curious Paul

Posted on • Edited on

Building An E-commerce Telegram Bot Using Python and Fauna.

This article will show you how to build a telegram bot for a small-scale business owner. This bot will be pretty similar to the popular platform WhatsApp Business in terms of its features. We will see how to implement those features and set up our database on Fauna’s serverless system to host user data.

Before we begin, let me inform you that I will be calling this bot telegram-business for the rest of this tutorial.

WHAT TELEGRAM-BUSINESS CAN DO

First off, there are two kinds of users: customers and business owners. The features of the bot are classified according to the typeof users allowed on the platform. The following includes the features of telegram-business:

CUSTOMER FEATURES:

  • Customers can view businesses from four different categories.
  • Customers can view products from any of these businesses.
  • Customers can place an order on a product.

BUSINESS OWNER FEATURES:

  • Business owners can create a catalog and add products.
  • Business owners will get notified when a user places an order.

Pre-Requisites

For this tutorial I’m going to be using Python3, as well as Fauna’s platform.To follow this tutorial, you’ll want to have Python installed and have an account with Fauna. If you do not have a Fauna account, you can signup on the website, here. It is in your best interest to have a fundamental knowledge of Python to follow this tutorial. I’ll also be using Cloudinary to store product images. You can sign up for a free Cloudinary account, here. Here’s a detailed list of the things we need for this bot:

  • Python3
  • A fauna account
  • A cloudinary account

Preparing the database

The first thing we want to do is to set up our database on Fauna. Head over to fauna.com and log in to your dashboard. Once logged in, click on the “New Database” button on the dashboard to create a new database.

Alt Text

This will bring you to a page just like in the image above. Fill in the required fields and click on “Save” to create a new database for our bot.

Creating Collections

Next, we’ll create three collections for each entity on our platform; Users, Businesses, and Products. Click on “New Collection.” It should take you to a new page like the one below:

Alt Text

Call this one Users and hit the “Save” button to create the collection. After doing that, create two other collections, Business, and Products. Once you’re done, you should now have three collections show up on the collections menu option.

Alt Text

Let’s move on to creating indexes for easy retrieval of information from our database.

Creating Indexes

For our e-commerce system, we’re going to have to create several indexes to help with getting the information we need faster. To create an index head over to the indexes menu option and click on the “New Index” button to create a new index. It should take you to a new page that looks similar to the one below:

Alt Text

The first index we want to make allows us to find users by their name. So we’ll pick the source collection, which is the User collection for this first index, and then give our index the name “user_by_name” then we choose the term we want to search on, which is the “name” of the user, and so we enter “name” in the term field, you can leave the values field empty as is. Once done, your form should look like the one below:

Alt Text

Next create three more indexes with the following information:

  • business_by_name: {source collection: Business, term: name}
  • product_by_business: {source collection: Product, term: sme}
  • business_by_category: {source collection: Business, term: category}

Generating an API_KEY

One last thing we need to get from Fauna is the API_KEY for our database to send queries to it over the internet. Head over to the Security option and click on the “New Key” option—it should take you to a new page with a form. Click “Save”, and it’ll generate a key for you to copy. Copy that key and save it in a text file somewhere we’ll use it later.

Building The Bot

Talking with BotFather

To build a Telegram bot, we must register it with Telegram’s bot father. To create your bot, open up Telegram and search for the bot father or simply click here to go to the bot father’s chat.

Once there, type “/start” to begin a conversation with the botfather, and it’ll show you a list of commands that you can use to interact with it; these commands can be typed or clicked on to execute them.

Alt Text

Choose the /newbot command to create a new bot. Once we do that, botfather will ask for a name and then a username. Once we do this, the botfather will provide us with information about the new bot, like the link to the bot and an access token which is what we need for now. Copy this and save it somewhere.

Alt Text

Scripting Telegram-Business

We'll start by creating a virtual environment for our project, opening up your command prompt, and changing the directory to your preferred location (this should preferably be a new empty folder). Once there, create a virtual environment using any desired virtual environment manager in Python. I’m using virtualenv to create one called “env” for example, I’ll run the following command:

virtualenv env

Next, we’ll activate our virtual environment and install the following python packages:

  • Python-telegram-bot: which helps us write code for controlling our bot.

  • Faunadb: which is a Python package that helps us interact with Fauna's api from our scripts.

  • python-dotenv: which will help us read config variables easily.

  • Cloudinary: a client library for our photo storage API. Make sure to sign up for a cloudinary account and get your api_key and secret from the dashboard. We'll need those later on.

A screenshot of the terminal showing the commands used to activate the virtual environment as well as the one for installing the listed out packages.

We’ll start by creating the following files in our project folder:

  • handlers.py
  • main.py
  • config.py
  • .env

I’ll split the implementation of the handlers into two parts :

  • Customer Functionalities
  • Business Functionalities

We’ll start with the business functionalities, which includes the bot’s interaction with users who are business owners. The first thing we need to do is to add our tokens and keys from botFather, cloudinary and Fauna to the .env file, as follows:

BOT_TOKEN=***486**:*******-****ro
API_SECRET=-****OcAGV****
API_KEY=********21
FAUNA_KEY=f****8sZ*****J****Gd4****Q
Enter fullscreen mode Exit fullscreen mode

Once this is done, we move on to the config.py file, and in there, we point some variables to these environment variables from the .env file by reading them via the python-dotenv package we installed earlier. This package allows us to read environment variables from files and even directly from the command prompt.

import os
from dotenv import load_dotenv

load_dotenv()

TOKEN = os.getenv('BOT_TOKEN')
api_secret = os.getenv('API_SECRET')
api_key = os.getenv('API_KEY')
FAUNA_KEY = os.getenv('FAUNA_KEY')
Enter fullscreen mode Exit fullscreen mode

Now, we can use these variables in our code without reading from the env file. Next, we begin with our handler methods, and these will handle how the bot interacts with the users on Telegram.

We’ll start with the handler methods responsible for managing the bot’s interaction with users who sign up to be business owners.. The file for this code is handlers.py, and import the required dependencies.

from telegram import (
    ReplyKeyboardMarkup,
    ReplyKeyboardRemove, Update,
    InlineKeyboardButton, InlineKeyboardMarkup
)
from telegram.ext import (
    CommandHandler, CallbackContext,
    ConversationHandler, MessageHandler,
    Filters, Updater, CallbackQueryHandler
)
from config import (
    api_key, sender_email,
    api_secret,
    FAUNA_KEY
)
import cloudinary
from cloudinary.uploader import upload
from faunadb import query as q
from faunadb.client import FaunaClient
from faunadb.errors import NotFound
Enter fullscreen mode Exit fullscreen mode

We’ll add some of the boilerplate config for some of our dependencies, so add the following after importing all these:

# configure cloudinary
cloudinary.config(
    cloud_name="curiouspaul",
    api_key=api_key,
    api_secret=api_secret
)

# fauna client config
client = FaunaClient(secret=FAUNA_KEY)

# Define Options
CHOOSING, CLASS_STATE, SME_DETAILS, CHOOSE_PREF, \
    SME_CAT, ADD_PRODUCTS, SHOW_STOCKS, POST_VIEW_PRODUCTS = range(8)
Enter fullscreen mode Exit fullscreen mode

The first config is for our Cloudinary instance, and the second for our Fauna client instance, the variables following that represent the states of the bot or at least the state that it could be in as users interact with it. This is required by the “conversationhandler” which is a method that allows us to script our bots in a way that makes their interaction with people more natural. The names of each variable slightly indicate what state they represent. For example, the SME_DETAILS state is tied to certain methods of asking the user for details of their business.

Adding Handler Methods

We’ll continue from this point by adding handler methods to our script. The first handler method we need to add is the one that handles what takes place when we initiate a conversation with the bot via the /start command.

The following lines of code are what gets called when someone uses the /start command on our bot, so add it to the handler code.

def start(update, context: CallbackContext) -> int:
    print("You called")
    bot = context.bot
    chat_id = update.message.chat.id
    bot.send_message(
        chat_id=chat_id,
        text= "Hi fellow, Welcome to SMEbot ,"
        "Please tell me about yourself, "
        "provide your full name, email, and phone number, "
        "separated by comma each e.g: "
        "John Doe, JohnD@gmail.com, +234567897809"
    )
    return CHOOSING
Enter fullscreen mode Exit fullscreen mode

Look at how I return a new state of the bot’s conversation, which is one of our predefined states from earlier. This function sends back an intro message and requests information from the user.

The following state is CHOOSING, and this state is tied to another method, which takes the user input and parses it to make sure it is correct before using the info to sign a user up. Then it prompts the user to pick what kind of user he/she is i.e., a customer or business owner. Add the following lines of code to add this method:

# get data generic user data from user and store
def choose(update, context):
    bot = context.bot
    chat_id = update.message.chat.id
    # create new data entry
    data = update.message.text.split(',')
    if len(data) < 3 or len(data) > 3:
        bot.send_message(
            chat_id=chat_id,
            text="Invalid entry, please make sure to input the details "
            "as requested in the instructions"
        )
        bot.send_message(
            chat_id=chat_id,
            text="Type /start, to restart bot"
        )
        return ConversationHandler.END
    #TODO: Check if user already exists before creating new user
    new_user = client.query(
        q.create(q.collection('User'), {
            "data":{
                "name":data[0],
                "email":data[1],
                "telephone":data[2],
                "is_smeowner":False,
                "preference": "",
                "chat_id":chat_id
            }
        })
    )
    context.user_data["user-id"] = new_user["ref"].id()
    context.user_data["user-name"] = data[0]
    context.user_data['user-data'] = new_user['data']
    reply_keyboard = [
    [
        InlineKeyboardButton(
            text="SME",
            callback_data="SME"
        ),
        InlineKeyboardButton(
            text="Customer",
            callback_data="Customer"
        )
    ]
  ]
  markup = InlineKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
    bot.send_message(
        chat_id=chat_id,
        text="Collected information succesfully!..🎉🎉 \n"
        "Which of the following do you identify as ?",
        reply_markup=markup
    )
    return CLASS_STATE

Enter fullscreen mode Exit fullscreen mode

This function takes the user input, parses it, and uses the result to save the user’s information onto our database on Fauna by using the query method from the Fauna library to send a post request to the Fauna API which writes to the database.

We also store some information about the user in memory with the context.user_data[] attribute - that we’ll need later in other handler methods. Lastly, we return a response as well as a new conversation state, this state (CLASS_STATE) is where we’ll determine what kind of user we’re dealing with i.e a customer or a business owner.

def classer(update, context):
    bot = context.bot
    chat_id = update.callback_query.message.chat.id
    name = context.user_data["user-name"]
    if update.callback_query.data.lower() == "sme":
        # update user as smeowner
        client.query(
            q.update(
                q.ref(q.collection("User"), context.user_data["user-id"]),
                {"data": {"is_smeowner":True}}
            )
        )
        bot.send_message(
            chat_id=chat_id,
            text=f"Great! {name}, please tell me about your business, "
            "provide your BrandName, Brand email, Address, and phone number"
            "in that order, each separated by comma(,) each e.g: "
            "JDWears, JDWears@gmail.com, 101-Mike Avenue-Ikeja, +234567897809",
            reply_markup=ReplyKeyboardRemove()
        )

        return SME_DETAILS
    categories = [  
        [
            InlineKeyboardButton(
                text="Clothing/Fashion",
                callback_data="Clothing/Fashion"
            ),
            InlineKeyboardButton(
                text="Hardware Accessories",
                callback_data="Hardware Accessories"
            )
        ],
        [
            InlineKeyboardButton(
                text="Food/Kitchen Ware",
                callback_data="Food/Kitchen Ware"
            ),
            InlineKeyboardButton(
                text="ArtnDesign",
                callback_data="ArtnDesign"
            )
        ]
    ]
    bot.send_message(
        chat_id=chat_id,
        text="Here's a list of categories available"
        "Choose one that matches your interest",
        reply_markup=InlineKeyboardMarkup(categories)
    )
    return CHOOSE_PREF

Enter fullscreen mode Exit fullscreen mode

However, the function sends a different response to the user if the user identifies as a customer instead. There is a menu of categories for the customer to choose from in order to view the businesses under the chosen category. If the user identifies as a customer these categories are sent as a response to the user and a new state is returned associated with customer functionalities.

Let's add a "cancel" command handler to help users cancel their interaction with the bot at any time. Add the following function next:

# Control
def cancel(update: Update, context: CallbackContext) -> int: 
    update.message.reply_text(
        'Bye! I hope we can talk again some day.',
        reply_markup=ReplyKeyboardRemove()
    )

    return ConversationHandler.END
Enter fullscreen mode Exit fullscreen mode

At this point, we can test our bot to see how it responds to our interactions. In order to test the bot, we need to register our handlers with the conversation handler method in main.py and map them to each state, as follows:

import handlers
from telegram.ext import (
    CommandHandler, CallbackContext,
    ConversationHandler, MessageHandler,
    Filters, Updater, CallbackQueryHandler
)
from config import TOKEN

updater = Updater(token=TOKEN, use_context=True)
print(updater)
dispatcher = updater.dispatcher


def main():
    conv_handler = ConversationHandler(
        entry_points=[CommandHandler('start', handlers.start)],
        states={
            handlers.CHOOSING: [
                MessageHandler(
                    Filters.all, handlers.choose
                )
            ],
            handlers.CLASS_STATE: [
                CallbackQueryHandler(handlers.classer)
            ]
        },
        fallbacks=[CommandHandler('cancel', handlers.cancel)],
        allow_reentry=True
    )
    dispatcher.add_handler(conv_handler)
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()
Enter fullscreen mode Exit fullscreen mode

Save this,go to your terminal, and run the bot via the main.py file as follows:

A screenshot showing the command to run the file from the terminal

Once you run this command, you might not see any immediate prompt from the terminal until you begin interacting with the bot. Let’s go to our bot and initiate a conversation. Use the link that botFather provided you with or just search with the name of your bot in Telegram.

Once there, use the /start command, or just click the start button on the chat, with your bot to initialize a conversation. You should see your bot respond as such:

A screenshot showing the response from our bot (telegram-business) as we've programmed it from earlier steps

Follow the prompt, type in the required information, and send it as a message to activate the next state of the conversation, which is going to ask you to pick what kind of user you identify as, an SME owner or a customer.

A screenshot showing the response from our bot  after we provide it with the details it asked for.

Choosing the SME option prompts us to enter details about our “business” as described in the classer() method we defined earlier on. However, if we chose the customer option as our preference, then the bot responds with a list of category options to choose from.

A screesnshot of another response from the bot prompting the user to enter details about his/her business just as we've programmed it.

This is as far as our bot can go, since we haven’t added the handler methods for the remaining functionalities we want. So let’s continue with scripting the bot.

The following is a series of handler methods that are responsible for all business functionalities. Add the following handler methods in the order that they are below. I am listing them out based on their functionalities, and some may involve more than one handler method:

  • Collecting Business Details and Storing: For this, there are two functions; the first takes user input from the last phase for a business, parses it and stores the data in memory, passes the data to the following handler method, and finally prompts the user to pick a category under which their business falls.

The second one takes all the data and uses it to create a new business document in our Faunadb instance, prompts the user to add a new product, and then returns a new
conversation state, which is associated with the handler methods responsible for adding products to the newly created business.

def business_details(update, context):
    bot = context.bot
    chat_id = update.message.chat.id
    data = update.message.text.split(',')
    if len(data) < 4 or len(data) > 4:
        bot.send_message(
            chat_id=chat_id,
            text="Invalid entry, please make sure to input the details "
            "as requested in the instructions"
        )
        return SME_DETAILS
    context.user_data["sme_dets"] = data
    # categories = [
    #         ['Clothing/Fashion', 'Hardware Accessories'],
    #         ['Food/Kitchen Ware', 'ArtnDesign'],
    #         ['Other']
    # ]
    categories = [  
        [
            InlineKeyboardButton(
                text="Clothing/Fashion",
                callback_data="Clothing/Fashion"
            ),
            InlineKeyboardButton(
                text="Hardware Accessories",
                callback_data="Hardware Accessories"
            )
        ],
        [
            InlineKeyboardButton(
                text="Food/Kitchen Ware",
                callback_data="Food/Kitchen Ware"
            ),
            InlineKeyboardButton(
                text="ArtnDesign",
                callback_data="ArtnDesign"
            )
        ]
    ]
    markup = InlineKeyboardMarkup(categories, one_time_keyboard=True)
    bot.send_message(
        chat_id=chat_id,
        text="Pick a category for your business from the options",
        reply_markup=markup
    )
    return SME_CAT

def business_details_update(update, context):
    bot = context.bot
    chat_id = update.callback_query.message.chat.id
    choice = update.callback_query.data
    # create business
    new_sme = client.query(
        q.create(
            q.collection("Business"),
            {"data":{
                "name":context.user_data["sme_dets"][0],
                "email":context.user_data["sme_dets"][1],
                "address":context.user_data["sme_dets"][2],
                "telephone":context.user_data["sme_dets"][3],
                "category":choice.lower()
            }}
        )
    )
    context.user_data["sme_name"] = context.user_data["sme_dets"][0]
    context.user_data["sme_id"] = new_sme["ref"].id()
    context.user_data["sme_cat"] = choice
    button = [[
        InlineKeyboardButton(
            text="Add a product",
            callback_data=choice.lower()
        )
    ]]
    bot.send_message(
        chat_id=chat_id,
        text="Business account created successfully, "
        "let's add some products shall we!.",
        reply_markup=InlineKeyboardMarkup(button)
    )
    return ADD_PRODUCTS
Enter fullscreen mode Exit fullscreen mode
  • Adding Products: This involves two handler methods as well, the first one prompts the user and gives instructions on how the product can be added, while the second one takes the user input, parses it and uses it to create a new product, by adding it as a new document to our Product collection on our Fauna instance.
def add_product(update, context):
    bot = context.bot
    chat_id = update.callback_query.message.chat.id
    bot.send_message(
        chat_id=chat_id,
        text="Add the Name, Description, and Price of product, "
        "separated by commas(,) as caption to the product's image"
    )
    return ADD_PRODUCTS

def product_info(update: Update, context: CallbackContext):
    data = update.message
    bot = context.bot
    photo = bot.getFile(update.message.photo[-1].file_id)
    file_ = open('product_image', 'wb')
    photo.download(out=file_)
    data = update.message.caption.split(',')
    # upload image to cloudinary
    send_photo = upload('product_image', width=200, height=150, crop='thumb')
    # create new product
    newprod = client.query(
        q.create(
            q.collection("Product"),
            {"data": {
                    "name":data[0],
                    "description":data[1],
                    "price":float(data[2]),
                    "image":send_photo["secure_url"],
                    "sme":context.user_data["sme_name"],
                    "sme_chat_id": update.message.chat.id,
                    "category":context.user_data["sme_cat"]
                }
            }
        )
    )
    # add new product as latest
    client.query(
        q.update(
            q.ref(q.collection("Business"), context.user_data["sme_id"]),
            {"data": {
                "latest": newprod["ref"].id()
            }}
        )
    )
    # context.user_data["product_data"] = newprod['data']
    button = [[InlineKeyboardButton(
        text='Add another product',
        callback_data=context.user_data["sme_name"]
    )]]
    update.message.reply_text(
        "Added product successfully",
        reply_markup=InlineKeyboardMarkup(button)
    )
    return ADD_PRODUCTS
Enter fullscreen mode Exit fullscreen mode

This concludes the business part of the bot. We will now add more handler methods to cover the customer functionalities, as follows:

  • View Businesses From any Category: This involves a handler method that takes the user’s choice and uses our business_by_category index to find all businesses in that category and then displays a list of the businesses with their latest merchandise as a thumbnail, along with two options.
## CUSTOMER
def customer_pref(update, context):
    bot = context.bot
    chat_id = update.callback_query.message.chat.id
    data = update.callback_query.data
    print(data)
    # get all businesses in category
    try:
        smes_ = client.query(
            q.map_(
                lambda var: q.get(var),
                q.paginate(
                    q.match(
                        q.index("business_by_category"),
                        str(data).lower()
                    )
                )
            )
        )
        print(smes_)
        for sme in smes_["data"]:
            button = [
                [
                    InlineKeyboardButton(
                        text="View Products",
                        callback_data=sme["data"]["name"]
                    )
                ],
                [
                    InlineKeyboardButton(
                        text="Select for updates",
                        callback_data="pref"+','+sme["data"]["name"]
                    )
                ]
            ]
            if "latest" in sme['data'].keys():
                thumbnail = client.query(q.get(q.ref(q.collection("Product"), sme["data"]["latest"])))
                print(thumbnail)
                bot.send_photo(
                    chat_id=chat_id,
                    photo=thumbnail["data"]["image"],
                    caption=f"{sme['data']['name']}",
                    reply_markup=InlineKeyboardMarkup(button)
                )
            else:
                bot.send_message(
                    chat_id=chat_id,
                    text=f"{sme['data']['name']}",
                    reply_markup=InlineKeyboardMarkup(button)
                )
    except NotFound:
        button = [[
            InlineKeyboardButton(
                text="Select another Category?",
                callback_data="customer"
            )
        ]]
        bot.send_message(
            chat_id=chat_id,
            text="Nothing here yet",
            reply_markup=InlineKeyboardMarkup(button)
        )
        return CLASS_STATE
    return SHOW_STOCKS
Enter fullscreen mode Exit fullscreen mode
  • View Products: This is quite self-explanatory, and it involves listing out all products belonging to any business that the user has decided to view. We are able to display all products from a business with the aid of the products_by_business index we created on our Fauna instance earlier, what this means is that we can send a query to fetch all the products that a business has on its catalogue (with the products_by_business index) and then display the results to the user.
def show_products(update, context):
    bot = context.bot
    chat_id = update.callback_query.message.chat.id
    data = update.callback_query.data
    if "pref" in  data:
        data = data.split(',')[0].replace(' ', '')
        print(data)
        user = client.query(
            q.get(
                q.ref(
                    q.match(q.index('user_by_name'), context.user_data['user-data']['name']),

                )
            )
        )
        # update preference
        client.query(
            q.update(
                q.ref(
                    q.collection('User'), user['ref'].id()
                ),
                {'data': {'preference': user['data']['preference']+data+','}}
            )
        )
        button = [
            [
                InlineKeyboardButton(
                    text="Vieew more businesses category",
                    callback_data='customer'
                )
            ]
        ]
        bot.send_message(
            chat_id=chat_id,
            text="Updated preference successfully!!"
        )
        return CLASS_STATE
    products = client.query(
        q.map_(
            lambda x: q.get(x),
            q.paginate(
                q.match(
                    q.index("product_by_business"),
                    update.callback_query.data
                )
            )
        )
    )
    # print(products)
    if len(products) <= 0:
        bot.send_message(
            chat_id=chat_id,
            text="'Nothing here yet, user hasn't added any products!, check back later"
        )
        return CLASS_STATE
    for product in products["data"]:
        context.user_data["sme_id"] = product['data']['sme']
        button = [
            [
                InlineKeyboardButton(
                    text="Send Order",
                    callback_data="order;" + product["ref"].id()
                )
            ],
            [
                InlineKeyboardButton(
                    text="Contact business owner",
                    callback_data="contact;" + product["data"]["sme"]
                )
            ]
        ]
        bot.send_photo(
            chat_id=chat_id,
            photo=product["data"]["image"],
            caption=f"{product['data']['name']} \nDescription: {product['data']['description']}\nPrice:{product['data']['price']}",
            reply_markup=InlineKeyboardMarkup(button)
        )
    return POST_VIEW_PRODUCTS
Enter fullscreen mode Exit fullscreen mode

This block of code is responsible for fetching all the products from a business and displaying them to the user, with the option of placing an order or contacting the business owner.

  • Place Order and Contact Details of Business Owner: The following block is responsible for placing an order and providing the customer with the business owner’s details.When a user places an order, the bot sends a message to the business owner containing information about the product that will sold, and the prospective buyer’s Telegram contact.
def post_view_products(update, context):
    bot = context.bot
    chat_id = update.callback_query.message.chat.id
    data = update.callback_query.data
    product = client.query(
        q.get(
            q.ref(
                q.collection("Product"),
                data.split(';')[1]
            )
        )
    )["data"]
    if "order" in data:
        bot.send_message(
            chat_id=product['sme_chat_id'],
            text="Hey you have a new order"
        )
        bot.send_photo(
            chat_id=product['sme_chat_id'],
            caption=f"Name: {product['name']}\n\nDescription: {product['description']}\n\nPrice: {product['price']}"
            f"\n\n Customer's Name: {context.user_data['user-name']}",
            photo=product['image']
        )  
        bot.send_contact(
            chat_id=product['sme_chat_id'],
            phone_number=context.user_data['user-data']['telephone'],
            first_name=context.user_data['user-data']['name']
        )
        bot.send_message(
            chat_id=chat_id,
            text="Placed order successfully"
        )
    elif 'contact' in data:
        sme_ = client.query(
            q.get( 
                q.match(
                    q.index("business_by_name"), 
                    product['sme']
                )
            )
        )['data']
        bot.send_message(
            chat_id=chat_id,
            text=f"Name: {sme_['name']}\n\nTelephone: {sme_['telephone']}\n\nEmail:{sme_['email']}"
        )

Enter fullscreen mode Exit fullscreen mode

Testing The Bot

We can save the changes to our handlers.py file and then move on to register the newly added methods to our conversation handler in main.py as with the first few functions, as follows:

import handlers
from telegram.ext import (
    CommandHandler, CallbackContext,
    ConversationHandler, MessageHandler,
    Filters, Updater, CallbackQueryHandler
)
from config import TOKEN

updater = Updater(token=TOKEN, use_context=True)
print(updater)
dispatcher = updater.dispatcher


def main():
    conv_handler = ConversationHandler(
        entry_points=[CommandHandler('start', handlers.start)],
        states={
            handlers.CHOOSING: [
                MessageHandler(
                    Filters.all, handlers.choose
                )
            ],
            handlers.CLASS_STATE: [
                CallbackQueryHandler(handlers.classer)
            ],
            handlers.SME_DETAILS: [
                MessageHandler(
                    Filters.all, handlers.business_details
                )
            ],
            handlers.SME_CAT: [
                CallbackQueryHandler(handlers.business_details_update)
            ],
            handlers.ADD_PRODUCTS: [
                CallbackQueryHandler(handlers.add_product),
                MessageHandler(Filters.all, handlers.product_info)
            ],
            handlers.CHOOSE_PREF: [
                CallbackQueryHandler(handlers.customer_pref)
            ],
            handlers.SHOW_STOCKS: [
                CallbackQueryHandler(handlers.show_products)
            ],
            handlers.POST_VIEW_PRODUCTS: [
                CallbackQueryHandler(handlers.post_view_products)
            ]
        },
        fallbacks=[CommandHandler('cancel', handlers.cancel)],
        allow_reentry=True
    )
    dispatcher.add_handler(conv_handler)
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()
Enter fullscreen mode Exit fullscreen mode

Update the code in main.py so it has the other functions we added, as shown above.When completed, we can run our code from the cmd, like before, to test our bot.

Once it's up and running, head over to the bot on Telegram and create a business and add products. You could also use another telegram account to interact with the bot as a customer and place orders.

A screenshot of the response from the telegram bot

A screenshot showing what the bot sends to the business owner when a new order is placed.

The second image shows what the business owner sees each time a customer places an order.

Conclusion

In this article, we’ve seen how to use Fauna’s serverless platform to create and host a database, along with indexes and collections. We saw how to generate an API key for communicating with our database from within other python apps, which in this case was telegram-business. We saw how to build a telegram bot from scratch using Python and the required libraries.

The demo from this article isn’t the end for telegram-business however, I’ll be adding more features and deploying it live soon enough. I’m hoping this article has inspired you to think of new ways to create solutions to problems around you, and when you finally find a solution you know you can always count on Fauna to help host your data without hassle like it has helped us in this article.

Top comments (33)

Collapse
 
shukkkur profile image
Shukur Sabzaliev

@curiouspaul1 Amazing job, really! I was wondering about further development. Do you have any post I could look into?

Collapse
 
curiouspaul1 profile image
Curious Paul • Edited

thanks for the kind words,
any post regarding what would you like me to help you with

Collapse
 
shukkkur profile image
Shukur Sabzaliev • Edited

@curiouspaul1 I tried to modify and use your Bot in my university so students can sell and buy things.
So, a few things I wish you could show/teach:
Divide posts/ads into categories but not businesses.
Also, I noticed that when fetching posts/ads from the databases sometimes it fails because, perhaps the user added unnecessary space or capitalization error (i tried to put a bunch of lower().strip() everywhere)
In addition, it would be amazing if SME and Customer both had the same functionality (both can buy and sell).
And I kinda failed to add Back buttons everywhere, but managed to add Exit button (just used you /cancel function)
Lastly, some buttons make require to be pressed two times, do you know why?

The bot: @ucaStudentStore_bot
Code link - (removing the link since you joined)

Thread Thread
 
curiouspaul1 profile image
Curious Paul

hmm, thanks a lot for pointing all of these out, I will work on them and once an upate is ready, i'll let you know

Thread Thread
 
curiouspaul1 profile image
Curious Paul

you're also welcome to join me in implementing these, i see you;ve forked the repository already. Btw there's an option to filter post/ads by category already.

Thread Thread
 
curiouspaul1 profile image
Curious Paul

here..

Thread Thread
 
shukkkur profile image
Shukur Sabzaliev

Hi! Thank you for responding! But I your message just says "here.." and that is? Is there a link or something?

Thread Thread
 
shukkkur profile image
Shukur Sabzaliev

I opened a issue in GitHub, in telegram-business repository please, check it. I need your help

Thread Thread
 
curiouspaul1 profile image
Curious Paul

oh theres supposed to be an image

Thread Thread
 
curiouspaul1 profile image
Curious Paul

alright great!

Collapse
 
maximpylnyk profile image
Maximpylnyk • Edited

Hello. I rewrote your main code and when I run the code, it says the following:
& C: /Users/maxpy/AppData/Local/Programs/Python/Python39/python.exe
d: /Plmenno/main.py
File "", line 1
& C: /Users/maxpy/AppData/Local/Programs/Python/Python39/python.exe
^ d: /Plmenno/main.py

SyntaxError: invalid syntax.
What should I do?

Collapse
 
curiouspaul1 profile image
Curious Paul

Hi thanks for stopping by, could you share your code on github with me so i can give it a look?

Collapse
 
maximpylnyk profile image
Maximpylnyk

you will help me?

Thread Thread
 
curiouspaul1 profile image
Curious Paul

yes sure sure, do you think we can schedule a google meets call and you can show me whats wrong on the call, send me an email: Paulcurious7@gmail.com

Collapse
 
maximpylnyk profile image
Maximpylnyk
Collapse
 
maximpylnyk profile image
Maximpylnyk

Ok

Collapse
 
alvaaz profile image
Álvaro Göede Rivera

Hi! I have problem when I try upload an image using Cloudinary, I don't know what happen. This is my code:

def add_media(update, context):
  send_photo = upload('http://res.cloudinary.com/demo/image/upload/couple.jpg')
Enter fullscreen mode Exit fullscreen mode

The console show:

2021-07-15 14:01:44,494 - telegram.ext.dispatcher - ERROR - No error handlers are registered, logging exception.
Traceback (most recent call last):
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/telegram/ext/dispatcher.py", line 555, in process_update
    handler.handle_update(update, self, check, context)
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/telegram/ext/conversationhandler.py", line 626, in handle_update
    new_state = handler.handle_update(update, dispatcher, check_result, context)
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/telegram/ext/handler.py", line 198, in handle_update
    return self.callback(update, context)
  File "/Users/alvarogoederivera/web/python_bot/handlers.py", line 64, in add_media
    send_photo = upload('http://res.cloudinary.com/demo/image/upload/couple.jpg')
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/cloudinary/uploader.py", line 46, in upload
    return call_cacheable_api("upload", params, file=file, **options)
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/cloudinary/uploader.py", line 394, in call_cacheable_api
    result = call_api(action, params, http_headers, return_error, unsigned, file, timeout, **options)
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/cloudinary/uploader.py", line 459, in call_api
    response = _http.request("POST", api_url, param_list, headers, **kw)
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/urllib3/request.py", line 78, in request
    return self.request_encode_body(
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/urllib3/request.py", line 155, in request_encode_body
    body, content_type = encode_multipart_formdata(
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/urllib3/filepost.py", line 78, in encode_multipart_formdata
    for field in iter_field_objects(fields):
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/urllib3/filepost.py", line 42, in iter_field_objects
    yield RequestField.from_tuples(*field)
  File "/Users/alvarogoederivera/web/python_bot/lib/python3.8/site-packages/urllib3/fields.py", line 181, in from_tuples
    filename, data = value
ValueError: not enough values to unpack (expected 2, got 1)
Enter fullscreen mode Exit fullscreen mode

Does anyone have any idea what is going on?

Collapse
 
curiouspaul1 profile image
Curious Paul

Hi, this doesn't seem like the code i wrote in the demo, for this article, however this doesn't look like the code to upload an image to cloudinary looks like. You might want to check the docs or use the one i wrote on the product_info method in handlers.py from the tutorial.

Collapse
 
alvaaz profile image
Álvaro Göede Rivera • Edited

I get the same problem when doing the demo, that's why I simplified it.

def add_media(update: Update, context: CallbackContext):
  logger.info(f"El usuario {update.effective_user['username']}, subió foto")
  quote = context.user_data["new_quote"]
  photo = context.bot.getFile(update.message.photo[-1].file_id)
  file_ = open("product_image", "wb")
  photo.download(out=file_)
  send_photo = upload("product_image", width=200, height=150, crop='thumb')
Enter fullscreen mode Exit fullscreen mode
dp.add_handler(ConversationHandler(
  entry_points = [CommandHandler("new", handlers.start)],
  states={
    handlers.QUOTE: [
      MessageHandler(
          Filters.all, handlers.add_quote
      )
    ],
    handlers.MEDIA: [
      MessageHandler(Filters.photo, handlers.add_media)
    ]
  },
  fallbacks=[CommandHandler('cancel', handlers.cancel)],
  allow_reentry=True)
)
Enter fullscreen mode Exit fullscreen mode

The console show the same error:

ValueError: not enough values to unpack (expected 2, got 1)
Enter fullscreen mode Exit fullscreen mode

I know the error is upload because when I remove it the error does not appear.

Thread Thread
 
curiouspaul1 profile image
Curious Paul

I see, do you add caption to the image when you test it though.?

Thread Thread
 
alvaaz profile image
Álvaro Göede Rivera

Could you check my code and see if there is something I am doing wrong?

github.com/alvaaz/telegram_bot

I would really appreciate it

Thread Thread
 
curiouspaul1 profile image
Curious Paul

oh great!, thanks for making this easier I'll take a look

Collapse
 
pradiepp profile image
pradiepp

Cant seems to solve this error while running the Main.py


Traceback (most recent call last):
File "C:\Users\ACER\Desktop\Bot\Bot\main.py", line 35, in
main()
File "C:\Users\ACER\Desktop\Bot\Bot\main.py", line 16, in main
entry_points=[CommandHandler('start', handlers.start)],
NameError: name 'handlers' is not defined. Did you mean: 'handler'?

Collapse
 
curiouspaul1 profile image
Curious Paul

did you rename the "handlers.py" file

Collapse
 
pradiepp profile image
pradiepp

No, its 'handlers.py' only.

Collapse
 
prodplug profile image
prodplug

File "\main.py", line 9, in
updater = Updater(token=TOKEN, use_context=True)
File "\updater.py", line 237, in init
raise ValueError('token or bot must be passed')
ValueError: token or bot must be passed

any way to fix this (removed directory for privacy purpose)

this is when i enter python main.py

Collapse
 
curiouspaul1 profile image
Curious Paul

You have to replace "TOKEN" with the actual token that you got from the telegram Bot-father and use that instead, i just put "TOKEN" as a placeholder

Collapse
 
balighmehrez profile image
BalighMehrez

Awesome

Just wanted to notify you forgot to define the market on choose method
reply_markup=markup

Collapse
 
curiouspaul1 profile image
Curious Paul

Oh thanks a lot man..i just noticed that

Collapse
 
niketoz3 profile image
Niketoz

========== RESTART: C:\Users\User\Desktop\botpol\botpol_env\main.py ==========
Traceback (most recent call last):
File "C:\Users\User\Desktop\botpol\botpol_env\main.py", line 1, in
import handlers
File "C:\Users\User\Desktop\botpol\botpol_env\handlers.py", line 1, in
from telegram import (
ModuleNotFoundError: No module named 'telegram'

Iam really stuck.

Collapse
 
codegien profile image
codegien

Awesome

Collapse
 
curiouspaul1 profile image
Curious Paul

Thanks a lot boss

Collapse
 
dhruv13723 profile image
Dhruv

I have copied the exact code still I am getting this error. please help me
This is the error: dev-to-uploads.s3.amazonaws.com/up...