To follow along this blog post, the repo is here

Introduction

I have a couple of previous blog post that talks about my journey in building chat bots with Webex and Discord, and using them as an AI assistant with the combination of LLM. In th past, I have also built chat bots on various other platforms like Line, Telegram, Slack, and WhatsApp.

With the current popularity of LLM, one way to interact with the model is through chat bots.

In this blog post, I will share with you the step by step guide to building a simple function chat bot on various platforms, and hopefully by the end of the post, you will be able to take away some ideas on how to build your own chat bot on your preferred platform and integrate with your own AI model!

I will go through the steps of setting up chat bots on the following platforms: Discord, Line, Slack, Telegram, Webex and WhatsApp.

The complete code for this blog post is here.

Discord

Step 1: Create a Discord Bot

To do so, we will have to create a Discord bot on the Discord Developer Portal.

Go on to the Discord Developer Portal and click on the "New Application" button. Give your application a name and click on the "Create" button.

png

Step 2: Code implementation with Python

There are various discord libraries that you can use to build your bot. In this example, I will be using the pycord library.

To follow along, you can find the code implementation on my GitHub.

The below code is a simple implementation of a Discord bot that listens to a !ping command and responds with a Pong! message. It shows how to create a normal command and a slash command.

from settings import app_config
from discord.ext import commands
import discord
import logging

logger = logging.getLogger(__name__)


status = discord.Status.online

bot = commands.Bot(
    command_prefix="!",
    intents=discord.Intents.all(),
    activity=discord.Activity(
        type=discord.ActivityType.competing, name="/help"
    ),
)


@bot.event
async def on_ready():
    logger.info(f"We have logged in as {bot.user}")


@bot.command(help="Simple ping command")
async def ping(ctx, *, args):
    received_msg = "".join(args)

    logger.info(f"Received message: {received_msg} from user {ctx.author} ")

    await ctx.send("Pong!")


@bot.slash_command(
    name="ping",
    guild_ids=[app_config.DC_SER_ID],
    description="Ping slash command",
)
async def ping(ctx):
    await ctx.defer()
    await ctx.respond("Pong!")

For further setup and production deploy, you can refer to my previous blog post on how I integrate the Discord bot with GCP Vertex AI and the corresponding code base on my GitHub here.

Line

Step 1: Create a Line Bot

To do so, we will have to create an application on the Line Developer Console.

1 - Create a provider. Click on the "Create Provider" button and fill in the necessary details.

png

2 - Click on the "Create New Channel" button, you will see there are different channel type, in our case, to create a Line bot, we will select the "Messaging API" channel type.

png

3 - Fill in the necessary details for our channel.

png

4 - After creating the channel, you will be able to see the some of the basic information of the channel.

png

If we navigate to Basic settings, we will be able to see the Channel Secret, and under messaging API, we will be able to see the Channel Access Token and the field to add a webhook URL. We will need these information to authenticate our bot.

Step 2: Code implementation with Python

We can use either Flask or FastAPI to create a webhook server to listen to the incoming messages from Line.

For this example, I will be using FastAPI and deploy the webhook server on GCP Cloud Run.

If you are interested in Flask implementation, you can refer to my old repo that I have created a Line bot with Flask and use ngrok for webhook during local development here.

To follow along, you can find the code implementation on my GitHub.

In the below code, I have created a simple webhook server that listens to incoming messages from Line and responds with a Pong! message when the user sends a !ping message, and if the user sends any other message, the bot will echo back with the same message.

from fastapi import FastAPI, HTTPException, Request

from linebot.v3.webhook import WebhookParser
from linebot.v3.messaging import (
    AsyncApiClient,
    AsyncMessagingApi,
    Configuration,
    ReplyMessageRequest,
    TextMessage,
)
from linebot.v3.exceptions import InvalidSignatureError
from linebot.v3.webhooks import (
    MessageEvent,
    TextMessageContent,
)

from settings import app_config
from os import getenv
import google.cloud.logging
import logging


if (env := getenv("ENV")) and env == "prod":
    client = google.cloud.logging.Client()
    client.setup_logging()

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(lineno)d} %(name)s - %(message)s",
)
logger = logging.getLogger(__name__)

app = FastAPI()

channel_secret = app_config.LINE_CHANNEL_SECRET
channel_access_token = app_config.LINE_TOKEN

configuration = Configuration(access_token=channel_access_token)

async_api_client = AsyncApiClient(configuration)
line_bot_api = AsyncMessagingApi(async_api_client)
parser = WebhookParser(channel_secret)


@app.get("/health")
async def health():
    return "ok"


@app.post("/line/events")
async def handle_callback(request: Request):
    signature = request.headers["X-Line-Signature"]

    body = await request.body()
    body = body.decode()

    try:
        events = parser.parse(body, signature)
    except InvalidSignatureError:
        raise HTTPException(status_code=400, detail="Invalid signature")

    for event in events:
        logging.info(event)
        if not isinstance(event, MessageEvent):
            continue
        if not isinstance(event.message, TextMessageContent):
            continue

        if event.message.text == "!ping":
            await line_bot_api.reply_message(
                ReplyMessageRequest(
                    reply_token=event.reply_token,
                    messages=[TextMessage(text="pong!")],
                )
            )
        else:
            await line_bot_api.reply_message(
                ReplyMessageRequest(
                    reply_token=event.reply_token,
                    messages=[TextMessage(text=event.message.text)],
                )
            )

    return "OK"

To deploy our webhook server on GCP Cloud Run, we will need to create a Dockerfile, skaffold.yaml and a service.yaml

Dockerfile

FROM python:3.10-slim

WORKDIR /usr/src/app

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    POETRY_VERSION=1.7.1 \
    USERNAME=nonroot

RUN adduser $USERNAME
USER $USERNAME

ENV HOME=/home/$USERNAME
ENV PATH="$HOME/.local/bin:$PATH"

RUN pip install pipx
RUN pipx install poetry==${POETRY_VERSION}

COPY ./poetry.lock pyproject.toml /usr/src/app/
RUN poetry install -nv --no-root

COPY creds creds
COPY app .
CMD ["poetry", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

skaffold.yaml

apiVersion: skaffold/v4beta2
kind: Config
metadata:
  name: line-bot-fastapi-app
build:
  artifacts:
  - image: us-central1-docker.pkg.dev/gcp-prj-123/my-app/line-bot-fastapi-app
    docker:
      dockerfile: Dockerfile
    platforms:
      - "linux/amd64"
profiles:
- name: production
  manifests:
    rawYaml:
    - deploy/production/service.yaml
deploy:
  cloudrun:
    projectid: gcp-prj-123
    region: us-central1

service.yaml

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: line-bot-fastapi-app
spec:
  template:
    spec:
      serviceAccountName: svc@gcp-prj-123.iam.gserviceaccount.com
      containers:
      - image: us-central1-docker.pkg.dev/gcp-prj-123/my-app/line-bot-fastapi-app
        ports:
        - name: http1
          containerPort: 80
        env:
        - name: GCP_PROJECT_ID
          value: gcp-prj-123
        - name: ENV
          value: prod

After setting up all the necessary files, we can deploy our webhook server on GCP Cloud Run by running the following command:

skaffold run -p production -v info

And here is the result of the Line bot responding to our message.

png

Slack

Step 1: Create a Slack Bot

1 - Create a Slack bot on the Slack API page.

2 - Start with Create an App

png

3 - Select From scratch

png

After setting up, you will see this page.

png

4 - Under OAuth & Permissions, you will be able to see the Bot Token Scopes, you can add the necessary scopes for your bot to interact with the Slack workspace, at the minimum, you will need the chat:write scope to send messages to the workspace.

png

5 - On event subscriptions, you can subscribe to the necessary events that you want the bot to listen to, in our case, we will be subscribing to the message.channels and app_mention event.

png

6 - Install the app to your workspace.

png

7 - On the slack channel, click on Add an App to add the bot to the channel that you want the bot to listen to.

png

Confirming adding the bot to the channel.

png

8 - Add the webhook URL to the event subscription. To add the webhook URL, Slack will send a verification request to the URL, you will need to respond with the challenge code to verify the URL.

An example of the verification code is as below:

if body.get("type") == "url_verification":
        return JSONResponse(
            content={"challenge": body.get("challenge")}, status_code=200
        )

png

Step 2: Code implementation with Python

Slack uses webhooks to subscribe to events and send messages to the bot, here we will be using the FastAPI to create a webhook server to listen to the incoming messages from Slack, the setup is similar to the Line bot and the service will be deployed on GCP Cloud Run. Again, we can use ngrok for local development.

To follow along, you can find the code implementation on my GitHub.

There is multiple ways to interact with the slack bot, you can use the slack_bolt library, or you can use the requests library to send messages to the Slack workspace.

1 - Implementation of using requests library to send messages to the Slack workspace:

from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse, PlainTextResponse
import httpx

from settings import app_config

from os import getenv
import google.cloud.logging
import logging


if (env := getenv("ENV")) and env == "prod":
    client = google.cloud.logging.Client()
    client.setup_logging()

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(lineno)d} %(name)s - %(message)s",
)
logger = logging.getLogger(__name__)

app = FastAPI()

SLACK_BOT_TOKEN = app_config.SLACK_BOT_TOKEN


@app.get("/health")
async def health():
    return "ok"


async def send_message_to_slack(channel_id, message):
    url = "https://slack.com/api/chat.postMessage"
    headers = {
        "Authorization": f"Bearer {SLACK_BOT_TOKEN}",
        "Content-type": "application/json",
    }
    payload = {"channel": channel_id, "text": message}
    async with httpx.AsyncClient() as client:
        await client.post(url, headers=headers, json=payload)


@app.post("/slack/events")
async def handle_events(request: Request):
    body = await request.json()

    if body.get("type") == "url_verification":
        return JSONResponse(
            content={"challenge": body.get("challenge")}, status_code=200
        )

    if body.get("type") == "event_callback":
        event = body.get("event", {})
        logger.info(f"Event: {event}")

        if (
            event.get("type") == "message"
            and "subtype" not in event
            and "bot_id" not in event
        ):
            channel_id = event.get("channel")
            message_text = event.get("text", "")
            user_id = event.get("user")

            logger.info(
                f"Channel ID: {channel_id}\nMessage Text: {message_text}\nUser ID: {user_id}"
            )

            if message_text.strip() == "!ping":
                await send_message_to_slack(channel_id, "pong!")
            else:
                await send_message_to_slack(
                    channel_id, f"{user_id} said: {message_text}"
                )

            return Response(status_code=200)

    return JSONResponse(content={}, status_code=200)

2 - Implementation of using slack_bolt library to interact with the Slack workspace:

from settings import app_config

from fastapi import FastAPI, Request
from slack_bolt import App
from slack_bolt.adapter.fastapi import SlackRequestHandler

from os import getenv
import google.cloud.logging
import logging


if (env := getenv("ENV")) and env == "prod":
    client = google.cloud.logging.Client()
    client.setup_logging()

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(lineno)d} %(name)s - %(message)s",
)
logger = logging.getLogger(__name__)


slack_app = App(
    token=app_config.SLACK_BOT_TOKEN,
    signing_secret=app_config.SLACK_SIGNING_SECRET,
)

handler = SlackRequestHandler(slack_app)

app = FastAPI()


@slack_app.event("message")
def handle_message_events(body, say, logger):
    logger.info(body)
    event = body.get("event", {})

    if "bot_id" in event:
        return

    text = event.get("text", "").strip()
    user_id = event.get("user")

    if text == "!ping":
        say("pong!")
    else:
        say(f"<@{user_id}> said: {text}")


@app.post("/slack/events")
async def endpoint(req: Request):
    return await handler.handle(req)


@app.get("/health")
async def health():
    return "ok"

Both of the code implementation will be able to interact with the Slack workspace in the same way.

The below is the result of the Slack bot responding to the message.

png

Telegram

Step 1: Create a Telegram Bot

To create a Telegram bot, you will need to create a bot with BotFather on Telegram.

1 - Initiate a chat with BotFather and type /newbot to create a new bot, and follow the instructions to create a new bot. You will be given a token to authenticate your bot.

png

Step 2: Code implementation with Python

Similar to Discord, there is a lot of libraries that you can use to create a Telegram bot, in this example, I will be using the python-telegram-bot library.

from telegram import Update
from telegram.ext import filters, MessageHandler, ApplicationBuilder, CommandHandler, ContextTypes

from settings import app_config

from os import getenv
import google.cloud.logging
import logging


if (env := getenv("ENV")) and env == "prod":
    client = google.cloud.logging.Client()
    client.setup_logging()

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(lineno)d} %(name)s - %(message)s",
)
logger = logging.getLogger(__name__)


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")


async def ping(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await context.bot.send_message(chat_id=update.effective_chat.id, text="pong!")


async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text)

if __name__ == '__main__':
    application = ApplicationBuilder().token(app_config.TELEGRAM_TOKEN).build()

    start_handler = CommandHandler('start', start)
    ping_handler = CommandHandler('ping', ping)
    echo_handler = MessageHandler(filters.TEXT & (~filters.COMMAND), echo)

    application.add_handler(start_handler)
    application.add_handler(ping_handler)
    application.add_handler(echo_handler)

    application.run_polling()

And here is the result of the Telegram bot responding to the message.

png

Webex

Step 1: Create a Webex Bot

1 - Go to the Webex Developer Portal

2 - Create a new bot by clicking on My Webex App and then Create a App

png

3 - Choose the bot type and fill in the necessary details.

png

png

4 - After creating the bot, you will be able to see the bot’s access token, you will need this token to authenticate your bot. You will only be able to see the token once, so make sure to save it somewhere safe.

Step 2: Code implementation with Python

To follow along, you can find the code implementation on my GitHub

We can setup a command.py file to create a command class that will be used to create different commands for the bot. Below is an example of a Ping command that will respond with a Pong! message when the user sends a !ping command.

from webex_bot.models.command import Command


class Ping(Command):
    def __init__(self):
        super().__init__(
            command_keyword="!ping",
            help_message="!ping - a simple ping command",
            card=None,
        )

    def execute(self, message, attachment_actions, activity):
        return "pong!"

Then in the main.py file, we can create a bot instance and add the command to the bot.

from webex_bot.webex_bot import WebexBot

from commands import *
from settings import app_config
from os import getenv
import logging
import google.cloud.logging

if (env := getenv("ENV")) and env == "prod":
    client = google.cloud.logging.Client()
    client.setup_logging()

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(lineno)d} %(name)s - %(message)s",
)
logger = logging.getLogger(__name__)

bot = WebexBot(app_config.WEBEX_TOKEN, approved_users=["user@email.com"])

bot.add_command(Ping())


if __name__ == "__main__":
    bot.run()

And here is the result of the Webex bot responding to the message.

png

WhatsApp

Step 1: Create a WhatsApp Bot

Creating a WhatsApp bot is a bit more complicated than the other platforms

1 - Create an App on Meta Developer Console

2 - On Add use case, select Other, then choose Business for app type.

png

png

3 - Fill in the necessary details for the app.

png

4 - After filling in all of the information for the app, you will reach a page about Add products to your app

png

5 - Add product to the app, select WhatsApp Business Platform API

png

6 - Adding required access to the application. We will first need to create a system users, then add assets to the system user under Apps.

png

7 - To obtain a long-lived access token, you will need to add assets under the user system we created from the above step. **NOTE: ** We need to allow ALL of the permissions for the app to be able to send messages to the WhatsApp number, only allowing messaging permissions will not work.

8 - After setting up all of the access, we will have to grab the required config.

For App ID and App Secret, navigate to App Dasboard under Basic of App settings

png

For the phone ID of our bot, we will have to grab it from API Setup under WhatsApp

png

Step 2: Code implementation with Python

To follow along, you can find the code implementation on my GitHub

Again, because the WhatsApp chat bot is using webhooks to listen to the incoming messages, we can use FastAPI to create a webhook server to listen to the incoming messages from WhatsApp.

We will first define the functions needed to process the response and messages.

The function process_whatsapp_message will process the incoming message and respond with a Pong! message when the user sends a !ping message, otherwise, the bot will echo back the same message.

helpres/whatsappapi.py

import logging
import requests
import json
from settings import app_config


logger = logging.getLogger(__name__)


class Whatsapp:
    def __init__(self) -> None:
        self.ACCESS_TOKEN = app_config.ACCESS_TOKEN
        self.VERSION = app_config.WHATSAPP_API_VERSION
        self.PHONE_NUMBER_ID = app_config.PHONE_NUMBER_ID

    @staticmethod
    def log_http_response(response):
        logger.info(f"Status: {response.status_code}")
        logger.info(f"Content-type: {response.headers.get('content-type')}")
        logger.info(f"Body: {response.text}")

    def send_message(self, data):
        headers = {
            "Content-type": "application/json",
            "Authorization": f"Bearer {self.ACCESS_TOKEN}",
        }

        url = (
            f"https://graph.facebook.com/{self.VERSION}/{self.PHONE_NUMBER_ID}/messages"
        )

        response = requests.post(url, data=data, headers=headers)
        self.log_http_response(response)

    @staticmethod
    def get_text_message_input(recipient, text):
        return json.dumps(
            {
                "messaging_product": "whatsapp",
                "recipient_type": "individual",
                "to": recipient,
                "type": "text",
                "text": {"preview_url": False, "body": text},
            }
        )

    @staticmethod
    def is_valid_whatsapp_message(body):
        return body.get("object") == "whatsapp_business_account" and any(
            "messages" in change.get("value", {})
            for entry in body.get("entry", [])
            for change in entry.get("changes", [])
        )

    def process_whatsapp_message(self, body):
        wa_id = body["entry"][0]["changes"][0]["value"]["contacts"][0]["wa_id"]
        message = body["entry"][0]["changes"][0]["value"]["messages"][0]
        message_body = message["text"]["body"]

        logger.info(
            f"Logs from process_whatsapp_message:"
            f"\nBody:{body}\nWA ID:{wa_id}\nMessage:{message}\nMessage:{message_body}"
        )

        if message_body.strip() == "!ping":
            response = "pong!"
        else:
            response = message_body.strip()

        data = self.get_text_message_input(wa_id, response)
        self.send_message(data)

Then in main.py

from settings import app_config
from helpers.whatsappapi import Whatsapp

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

from os import getenv
import google.cloud.logging
import logging


if (env := getenv("ENV")) and env == "prod":
    client = google.cloud.logging.Client()
    client.setup_logging()

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(lineno)d} %(name)s - %(message)s",
)
logger = logging.getLogger(__name__)

app = FastAPI()


whatsappapi = Whatsapp()

VERIFICATION_TOKEN = app_config.VERIFICATION_TOKEN

@app.get("/health")
async def health():
    return "ok"


@app.get("/whatsapp/events")
async def verify(request: Request):
    mode = request.query_params.get("hub.mode")
    token = request.query_params.get("hub.verify_token")
    challenge = request.query_params.get("hub.challenge")
    logger.info(f"Mode: {mode}\nToken: {token}\nChallenge: {challenge}")

    if mode == "subscribe" and token == VERIFICATION_TOKEN:
        return JSONResponse(content=int(challenge))
    else:
        raise HTTPException(status_code=403, detail="Verification token mismatch")


@app.post("/whatsapp/events")
async def webhook(request: Request):
    body = await request.json()
    logger.info(f"Logs from webhook\nBody received: {body}")
    if whatsappapi.is_valid_whatsapp_message(body):
        whatsappapi.process_whatsapp_message(body)
    return JSONResponse(content={"status": "received"})

And here is the result of the WhatsApp bot responding to the message.

png

And that’s it! We have successfully created a chat bot on 6 communication platforms, Discord, Line, Slack, Telegram, Webex, and WhatsApp. To further enhance the chat bot, you can integrate it with your own AI model to provide more intelligent responses to the user’s queries. Please check by my previous blog post on how I integrate OpenAI GPT with Webex and Gemini with Discord chat bots.

I hope you find this blog post helpful and informative. Thank you for reading and have a nice day!

If you want to support my work,

Buy me a coffee

Honestly, if you made it this far you already made my day :)