Python Telegram Bot: InlineKeyboardButton and waiting for user input

2k Views Asked by At

I'm trying to make a telegram bot that will interface with an IoT system i'm developing. Actually i'm stuck in programming the python telegram bot; her's my problem: inside the bot's chat the user is able recall the InlineKeyboardButtons to turn on/off a light or set a timer. When the user choose "timer" buttone i would like to open another InlineKeyboardButtons to make the user select other parameters but with my current code i'm not able to to this beacuse the code jumps to the "callback handler". Any suggestion? I'll post the whole code:

from MyMQTT import *
import time
from datetime import datetime, timedelta
import json
import requests
import random
import telepot
from telepot.loop import MessageLoop
from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup
test=1

class tg_pub(object):
 
    def __init__(self, clientID, broker, port, topic):
        self.clientID = clientID
        # self.topic = str(topic)
        # self.topic = '/'.join([self.topic])
        self.client = MyMQTT(clientID, broker, port, self)
        self.__message = {
                "bn": "TelegramBot",
                "ip": "",
                "e": [
                    {
                        "n": "lights_command",
                        "u": "boolean",
                        "t": "2021-04-15 16:59:12.586822",
                        "v": "ON"
                    },
                    {
                        "n": "conditioner_command",
                        "u": "boolean",
                        "t": "2021-04-15 16:59:12.586822",
                        "v": "OFF"
                    }
                ]
            }

    def sendData(self, topic, output):
        message = self.__message
        message['e'][0]['v'] = output
        message['e'][0]['t'] = str(datetime.now())
        self.client.myPublish(topic, message)

    def start(self):
        self.client.start()

    def stop(self):
        self.client.stop()

class Room_Bot:
    def __init__(self, token, broker, port, topic):
        # the bot token is to be saved in the Catalog  !!!!
        self.tokenBot = token    # local token
        # self.tokenBot = requests.get("http://catalogIP/telegram_token").json()["telegramToken"]      # Catalog token
        self.bot= telepot.Bot(self.tokenBot)
        self.client = MyMQTT("telegramBot", broker, port, None)   # to be a publisher
        self.client.start()
        self.topic = topic
        self.__message={
                "bn": "TelegramBot",
                "ip": "",
                "e": [
                    {
                        "n": "lights_command",
                        "u": "boolean",
                        "t": "2021-04-15 16:59:12.586822",
                        "v": "ON"
                    },
                    {
                        "n": "conditioner_command",
                        "u": "boolean",
                        "t": "2021-04-15 16:59:12.586822",
                        "v": "OFF"
                    }
                ]
            }

        ### USING MESSAGES ###
        # MessageLoop(self.bot,{'chat': self.on_chat_message}).run_as_thread()

        ### USING BUTTONS -> query callback ###
        MessageLoop(self.bot, {'chat': self.on_chat_message, 'callback_query': self.on_callback_query}).run_as_thread()

    def sendMessage(self, topic):
        message=self.__message
        message['e'][0]['v']=random.randint(0,1)  # 0 = no movement detected
        message['e'][0]['t']=str(datetime.now())
        self.client.myPublish(topic, message)

    def start (self):
        self.client.start()

    def stop (self):
        self.client.stop()

    #HANDLING THE MESSAGES
    def on_chat_message(self, msg): 
        content_type, chat_type ,chat_ID = telepot.glance(msg)
        message = msg['text'] #retrieve the message from the key 'text' in the json
        
        ### USING MESSAGES ###
        # if message == "/switch_Lights_ON":
        #     payload = self.__message.copy()
        #     payload['e'][0]['v'] = "on"
        #     payload['e'][0]['t'] = str(datetime.now())
        #     self.client.myPublish(self.topic,payload)
        #     self.bot.sendMessage(chat_ID,text = "Lights switched on")
        # else:
        #     self.bot.sendMessage(chat_ID, text="Command not supported")

        ## USING BUTTONS ###
        if message == "/start":
            #self.bot.sendMessage(chat_ID, text="Welcome, I am HioTel. Please press one of the available buttons to get started.")
            keyboard = ReplyKeyboardMarkup(keyboard=[['Lights', 'Heater'], ['Status Info']])
            
            self.bot.sendMessage(chat_ID, text='Welcome, I am HioTel. Please press one of the available buttons to get started.', reply_markup=keyboard)

        elif message == "Lights":

            #Matrix of buttons
            keyboard = InlineKeyboardMarkup(inline_keyboard=[
                [
                    InlineKeyboardButton(text=f'Lights on ', callback_data=f'direct ON '),
                    InlineKeyboardButton(text=f'Lights off ⚫', callback_data=f'direct OFF')
                    ],
                    [
                        InlineKeyboardButton(text=f'Set timer ⏲️', callback_data=f'light_timer',test=0)
                    ]
                    ])
            self.bot.sendMessage(chat_ID, text='Lights menu:', reply_markup=keyboard) #REPLY TO USER

            
            

        elif message == "Heater":
            keyboard = InlineKeyboardMarkup(inline_keyboard=[
                [
                    InlineKeyboardButton(text=f'Heat on ', callback_data=f'heater_on'),
                    InlineKeyboardButton(text=f'Heat off ⚫', callback_data=f'heater_off')
                    ],
                    [
                    InlineKeyboardButton(text=f'Heat up ☀️', callback_data=f'heat_up'),
                    InlineKeyboardButton(text=f'Heat down ❄️', callback_data=f'heat_down')
                    ],
                    [
                        InlineKeyboardButton(text=f'Set timer ⏲️', callback_data=f'heater_timer')
                    ]
                    ])
            self.bot.sendMessage(chat_ID, text='Heater menu:', reply_markup=keyboard)


        elif message == "Status Info":
            self.bot.sendMessage(chat_ID, text=' Lights: ON\n\n Air conditioner: ON, set on tot°\nCurrent room conditions:\n tot° - umidiccio')
        else:
            self.bot.sendMessage(chat_ID, text="Command not supported")

    #HANDLING THE CALLBACKS 
    def on_callback_query(self, msg):
        query_ID , chat_ID , query_data = telepot.glance(msg, flavor='callback_query')
        payload = self.__message.copy()
        #if query_data=="light_timer":
            #keyboard = InlineKeyboardMarkup(inline_keyboard=[
                #[
                   # InlineKeyboardButton(text=f'5 minutes', callback_data=f'durati ON  5'),
                    #InlineKeyboardButton(text=f'15 minutes', callback_data=f'durati ON  15')
                    #],
                   # [
                     #   InlineKeyboardButton(text=f'30 minutes ', callback_data=f'durati ON  30')
                    #]
                   # ])
           # self.bot.sendMessage(chat_ID, text='Set timer:', reply_markup=keyboard)
            
        payload['e'][0]['v'] = query_data #SET AS MESSAGE WHAT USER SELECTED IN CHAT BUTTONS 
        payload['e'][0]['t'] = str(datetime.now())

        self.client.myPublish(self.topic, payload) #PUBLISH MQTT MESSAGE
        self.bot.sendMessage(chat_ID, text=f"{query_data}: done ✔") #FEEDBACK FOR THE USER
        print(test)



if __name__ == '__main__':
    
    url = "xxxxxx"
    clientID = 'telegram'

    settings = requests.post(url)
    
    config = list(settings.json())
    broker = config[0]["settings"][0]["broker"]
    port = config[0]["settings"][0]["port"]
    token = config[0]["settings"][0]["botToken"]
    baseTopic = config[0]["settings"][0]["baseTopic"]
    topic_pub_l = baseTopic +  config[0]["settings"][0]["lightControlTopic"]
    topic_pub_c = baseTopic +  config[0]["settings"][0]["conditControlTopic"]
    
    topic = baseTopic + topic_pub_l

    pub_l = tg_pub(clientID, broker, port, topic_pub_l)
    pub_l.start()

    output = None
    pub_l.sendData("hotel/catalog", output)
    time.sleep(random.randint(1,10))
    requests.post(url)
    

    bot = Room_Bot(token,broker,port,topic)
    
    while True:
        time.sleep(2)
1

There are 1 best solutions below

0
On

That's correct as pressing on any of the buttons (InlineKeyboardButtons ) will trigger the CallbackHandler which is responsible to process the response.

One option is to refactor your code to create methods to send a given response, for example

   def send_lights(chat_ID) :
     keyboard = InlineKeyboardMarkup(inline_keyboard=[
            [
                InlineKeyboardButton(text=f'Heat on ', callback_data=f'heater_on'),
                InlineKeyboardButton(text=f'Heat off ⚫', callback_data=f'heater_off')
                ],
                [
                InlineKeyboardButton(text=f'Heat up ☀️', callback_data=f'heat_up'),
                InlineKeyboardButton(text=f'Heat down ❄️', callback_data=f'heat_down')
                ],
                [
                    InlineKeyboardButton(text=f'Set timer ⏲️', callback_data=f'heater_timer')
                ]
                ])
        self.bot.sendMessage(chat_ID, text='Heater menu:', reply_markup=keyboard)

You can invoke the new method (send_lights) from the CallbackHandler when the user presses a given button.

A second option would be to have a single method which processes both messages and callbacks: I am not sure how you do that with telepot to be honest, but with PythonTelegramBot is possible: in this case you deal with all responses in the same place.