Create a Weather Bot in Telegram Using Python

Telegram, one of the most popular messaging applications, is a bit more flexible than other popular chat applications like WhatsApp or Messenger by Facebook. Telegram is a cross-platform instant messaging service that also supports video calls, VOIP, and sharing files with your friends. Launched in 2013, Telegram now has more than 300+ million active users.

One of Telegram’s best features is storing messages on the server, not on your local device. The Telegram API is also open for developers to leverage. With the right APIs, you can power your chatbots with all sorts of interactive chat-based insights, all from within the Telegram chat environment.

In this article, we’ll explore how to use the Telegram API to build a chatbot that displays your current weather forecast. We’ll overview getting Telegram API credentials, initiating a bot, writing a script to call the OpenWeatherMap API, and coding functions in Python.

Prerequisites:

  • Basic knowledge of Python
  • Telegram Account
  • OpenWeather Account

Step –1: Get API credentials

We’ll be covering how to get Telegram API credentials you can get directly from your mobile app, but I would suggest you go to web.telegram.org and initiate a chat with this username BotFather.

So look for Bot Father and send this message: /newbot

Once you hit enter, the BotFather will ask you this question:

“Alright, a new bot. How are we going to call it? Please choose a name for your bot.”

Let’s give it the name of “WeatherBot”

Next, it’ll ask, “Good. Now let’s choose a username for your bot. It must end in bot. Like this, for example, TetrisBot or tetris_bot.”

Here you have to make sure that it ends with “bot”, so give it a name. For instance, I am giving it “geekyhumans_weather_bot”.

On success, it’ll reply with a message like this:

Done! Congratulations on your new bot. You will find it at t.me/geekyhumans_weather_bot. You can now add a description, about section, and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.

Use this token to access the HTTP API:
XXXXXXXXXXXXXXXXXXXX
Keep your token secure and store it safely, it can be used by anyone to control your bot.

For a description of the Bot API, see this page: https://core.telegram.org/bots/api

Now store your API credentials in a safe place. We’ll be using this key to send messages.

Step –3: Getting Weather API credentials

In this tutorial, we’ll be using OpenWeatherMap API, one of the most popular weather APIs. Sign up with OpenWeatherMap here, create your account, and verify your email.

Once done, log in to the website, and you’ll see something like this:

Here click on API Keys and copy your API key from there.

Step –3: Code Setup:

Once you’re done with all the above tasks, let’s set up our project now. So open your terminal and create a new folder by using the below command:

mkdir telegram-api

And then open the folder:

cd telegram-api

Now it’s time to install some dependencies. So on your terminal, paste the below code:

python3 -m pip install requests

We’re installing requests here because it makes it very easy to make API calls.

Once that is done, create a new file by telegram.py and open it in your favorite editor. Now it’s time to write some code!

Let’s start with importing some modules, so paste the below code to import the modules:

import json 
import requests
import time
import urllib 
import logging
import signal
import sys

TOKEN = "API_KEY FOR TELEGRAM"
OWM_KEY = "OPEN WEATHER API KEY"
POLLING_TIMEOUT = None

Code Explanation:

Here we’re importing few necessary modules like json for parsing the JSON, requests to make API calls to Telegram as well as OpenWeatherAPI, time to use time.sleep() which will help in polling, urllib to parse the url-encoded text.

Once the above is done, we’re going to define some Lambda functions. Don’t know about lambda functions? Well, they’re anonymous functions that can take a number of parameters and only one expression.

# Lambda functions to parse updates from Telegram
def getText(update):            return update["message"]["text"]
def getLocation(update):        return update["message"]["location"]
def getChatId(update):          return update["message"]["chat"]["id"]
def getUpId(update):            return int(update["update_id"])
def getResult(updates):         return updates["result"]

# # Lambda functions to parse weather responses
def getDesc(w):                 return w["weather"][0]["description"]
def getTemp(w):                 return w["main"]["temp"]
def getCity(w):                 return w["name"]
logger = logging.getLogger("weather-telegram")
logger.setLevel(logging.DEBUG)

# Cities for weather requests
cities = ["London", "Brasov"]
def sigHandler(signal, frame):
    logger.info("SIGINT received. Exiting... Bye bye")
    sys.exit(0)

Code Explanation:

We have defined a lot of Lambda functions, and each function is used to return different data. For example getText() is used for fetching the text from message in Telegram, getLocation() is used to return the location from message, getChatId() is used to return the chat id of the message, getUpId returns the update id, getResult returns the result.

Similarly, there are Lambda functions for the Weather API like, getDesc() return the description of the weather, getTemp() returns the Temperature, getCity will return the city.

We also have defined a list of cities that will appear by default in Telegram.

So far, we have created the API key and initialized all the functions. Now let’s dive into the main functionality.

Step –4: Defining All the Functions:

Now we’ll define all the functions which we’ll be using for our Weather Bot. So paste the below code:

# Configure file and console logging
def configLogging():
    # Create file logger and set level to DEBUG
    # Mode = write -> clear existing log file
    handler = logging.FileHandler("run.log", mode="w")
    handler.setLevel(logging.DEBUG)
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    # Create console handler and set level to INFO
    ch = logging.StreamHandler()
    ch.setLevel(logging.INFO)
    formatter = logging.Formatter("[%(levelname)s] - %(message)s")
    ch.setFormatter(formatter)
    logger.addHandler(ch)

def parseConfig():
    global URL, URL_OWM, POLLING_TIMEOUT
    URL = "https://api.telegram.org/bot{}/".format(TOKEN)
    URL_OWM = "https://api.openweathermap.org/data/2.5/weather?appid={}&units=metric".format(OWM_KEY)
    POLLING_TIMEOUT

Code Explanation:

Here we’re defining our logger, this will log any error, access, with the and message. It’ll create a file run.log and will save each and every log there in a formatted manner. Here’s an example:

2020-12-13 02:56:36,450 - INFO - Getting updates
2020-12-13 02:56:36,450 t - DEBUG - URL: https://api.telegram.org/bot1482273391:XXXXXXX/getUpdates?timeout=None
2020-12-13 02:56:41,082 - DEBUG - {'keyboard': [[{'text': 'London'}], [{'text': 'Brasov'}], [{'text': 'Share location', 'request_location': True}]], 'one_time_keyboard': True}

Now we’ll define three more functions, makeRequest(), getUpdates(), buildkeyboard(), buildCitiesKeyboard(). So paste the below code in the same file:

# Make a request to Telegram bot and get JSON response
def makeRequest(url):
    logger.debug("URL: %s" % url)
    r = requests.get(url)
    resp = json.loads(r.content.decode("utf8"))
    return resp

# Return all the updates with ID > offset
# (Updates list is kept by Telegram for 24h)
def getUpdates(offset=None):
    url = URL + "getUpdates?timeout=%s" % POLLING_TIMEOUT
    logger.info("Getting updates") 
    if offset:
        url += "&offset={}".format(offset)
    js = makeRequest(url)
    return js

# Build a one-time keyboard for on-screen options
def buildKeyboard(items):
    keyboard = [[{"text":item}] for item in items]
    replyKeyboard = {"keyboard":keyboard, "one_time_keyboard": True}
    logger.debug(replyKeyboard)
    return json.dumps(replyKeyboard)

def buildCitiesKeyboard():
    keyboard = [[{"text": c}] for c in cities]
    keyboard.append([{"text": "Share location", "request_location": True}])
    replyKeyboard = {"keyboard": keyboard, "one_time_keyboard": True}
    logger.debug(replyKeyboard)
    return json.dumps(replyKeyboard)

Code Explanation:

The function makeResuest() is a kind of universal function where we just have to pass the URL, and it’ll make a GET request and return the response in JSON. The function getUpdates() returns the updates for the logger with polling timeout. We also have defined two more functions, buildKeyboard() and buildCitiesKeyboard(), which will create a custom keyboard, which we’ll show you later in this tutorial.

Now we’ll add some more functions related to Weather API and Telegram API. So add the below code:

# Query OWM for the weather for place or coords
def getWeather(place):
    if isinstance(place, dict):     # coordinates provided
        lat, lon = place["latitude"], place["longitude"]
        url = URL_OWM + "&lat=%f&lon=%f&cnt=1" % (lat, lon)
        logger.info("Requesting weather: " + url)
        js = makeRequest(url)
        logger.debug(js)
        return u"%s \N{DEGREE SIGN}C, %s in %s" % (getTemp(js), getDesc(js), getCity(js))
    else:                           # place name provided 
        # make req
        url = URL_OWM + "&q={}".format(place)
        logger.info("Requesting weather: " + url)
        js = makeRequest(url)
        logger.debug(js)
        return u"%s \N{DEGREE SIGN}C, %s in %s" % (getTemp(js), getDesc(js), getCity(js))

# Send URL-encoded message to chat id
def sendMessage(text, chatId, interface=None):
    text = text.encode('utf-8', 'strict')                                                       
    text = urllib.parse.quote_plus(text)
    url = URL + "sendMessage?text={}&chat_id={}&parse_mode=Markdown".format(text, chatId)
    if interface:
        url += "&reply_markup={}".format(interface)
    requests.get(url)

# Get the ID of the last available update
def getLastUpdateId(updates):
    ids = []
    for update in getResult(updates):
        ids.append(getUpId(update))
    return max(ids)

Code Explanation:

Here we have defined three more functions: getWeather(), which takes the longitude and latitude of the current location and then makes the request to Weather API and returns the temperature, description, and city. The function sendMessages() parses the message into url-encoded format and calls to Telegram API, then sends the message to Telegram. The function getLastUpdatedID() returns the id of the latest message; we’re doing this so our bot doesn’t get confused with the older messages while polling for a new message.

Now we’re going to add two last functions, and then our bot is ready. So add these two functions:

# Keep track of conversation states: 'weatherReq'
chats = {}

# Echo all messages back
def handleUpdates(updates):
    for update in getResult(updates):
        chatId = getChatId(update)
        try:
            text = getText(update)
        except Exception as e:
            logger.error("No text field in update. Try to get location")
            loc = getLocation(update)
            # Was weather previously requested?
            if (chatId in chats) and (chats[chatId] == "weatherReq"):
                logger.info("Weather requested for %s in chat id %d" % (str(loc), chatId))
                # Send weather to chat id and clear state
                sendMessage(getWeather(loc), chatId)
                del chats[chatId]
            continue

        if text == "/weather":
            keyboard = buildCitiesKeyboard()
            chats[chatId] = "weatherReq"
            sendMessage("Select a city", chatId, keyboard)
        elif text == "/start":
            sendMessage("Cahn's Axiom: When all else fails, read the instructions", chatId)
        elif text.startswith("/"):
            logger.warning("Invalid command %s" % text)    
            continue
        elif (text in cities) and (chatId in chats) and (chats[chatId] == "weatherReq"):
            logger.info("Weather requested for %s" % text)
            # Send weather to chat id and clear state
            sendMessage(getWeather(text), chatId)
            del chats[chatId]
        else:
            keyboard = buildKeyboard(["/weather"])
            sendMessage("I learn new things every day but for now you can ask me about the weather.", chatId, keyboard)

def main():
    # Set up file and console loggers
    configLogging()



    # Get tokens and keys
    parseConfig()
 
    # Intercept Ctrl-C SIGINT 
    signal.signal(signal.SIGINT, sigHandler) 
 
    # Main loop
    last_update_id = None
    while True:
        updates = getUpdates(last_update_id)
        if len(getResult(updates)) > 0:
            last_update_id = getLastUpdateId(updates) + 1
            handleUpdates(updates)
        time.sleep(0.5)

if __name__ == "__main__":
    main()

Code Explanation:

We have defined a function handleUpdates() which handles all the processing. It calls getResult() to get the id of the latest message. If there’s a new message, it’ll call getText() to get the text from the message. It checks if the city was previously searched in the past. If yes, then it’ll simply make an API call and return the weather information. If it matches the /weather string then it’ll return the custom keyboard. On selection of the city, it’ll make the API call and return the weather info. If there’s no text in the message, it’ll try to get the current location and then try to make the API call depending on the location.

We have got the main() function too. This will call the other functions in the correct sequence and runs by default.

Now that we have completed the coding part, we can now move ahead with the testing part.

Step –5: Testing our Bot:

Let’s run the code so on your terminal execute the below code:

python3 telegram.py

And then, open your Telegram App and search for the username that you chose for the bot. Now tap on the bot and write /weather. It’ll show you something like this:

From here, you can select any predefined city, or share your own location. Let’s go for Brasov, and it’ll return the current weather:

Final Words:

We have successfully created the Telegram Weather Bot. The Telegram API can do a lot more than just returning the current weather. You can even create a bot that can return current stock prices or anything you want. You can try to explore the API and try to implement new functionalities using the API.