Skip to main content

Tutorial: Weather Bot

A weather assistant using the openweather library, with saved locations and a scheduled daily forecast. Based on the weather.botgami blueprint.

What you'll learn​

  • Importing and calling a library with on error
  • Saving per-user data (user.*)
  • A scheduled flow that DMs a forecast

Step 1: Setup​

Put your OpenWeatherMap key in a global.* variable (set the real value in the Variables panel).

import openweather from "openweather"

bot WeatherBot {
meta { name: "Weather Bot" slug: "weather-bot" icon: "🌤" }

global openweather_api_key: String = ""

user home_city: String = ""
// For the scheduler to DM each user (it can't read other users' user.*).
shared subscribers: Array<{ uid: Number, city: String }> = []
}

Step 2: Check the weather​

Ask for a city, call the library, handle failures gracefully.

flow weather on command "/weather" {
branch ask.text("Which city?", timeout: "60s") {
"timeout" { send.text("No city given — try /weather again.") stop }
default {
set { flow.city = str.trim(answer) }
let { temp, description, humidity, city_name } = await openweather.current_weather(
city: flow.city,
api_key: global.openweather_api_key
) on error {
send.text(`āš ļø Couldn't find weather for "${flow.city}". Check the spelling.`)
stop
}
send.text(`🌤 *${city_name}*\n${temp}°C, ${description}\nšŸ’§ Humidity: ${humidity}%`, parse_mode: "Markdown")
}
}
}

Step 3: Save a home city​

flow set_home on command "/sethome" {
branch ask.text("What's your home city?", timeout: "60s") {
"timeout" { send.text("Cancelled.") stop }
default {
set { user.home_city = str.trim(answer) }
set {
shared.subscribers = array.reject(shared.subscribers, "uid", user.id)
shared.subscribers << [{ uid: user.id, city: user.home_city }]
}
send.text(`āœ… Home city set to ${user.home_city}. You'll get a daily forecast at 07:00.`)
}
}
}

We mirror the city into shared.subscribers so the scheduled flow (which can't read other users' user.*) knows whom to message.

Step 4: Daily forecast​

A scheduled flow loops the subscribers and DMs each one. Scheduled flows have no chat in scope, so we pass chat_id explicitly.

flow daily_forecast on schedule "0 7 * * *" {
foreach sub in shared.subscribers {
set { flow.skip = false }
let { temp, description, city_name } = await openweather.current_weather(
city: sub.city,
api_key: global.openweather_api_key
) on error {
// on error doesn't stop the flow — set a flag and skip the success send.
set { flow.skip = true }
send.text("āš ļø Couldn't fetch your forecast today.", chat_id: sub.uid)
}
when !flow.skip {
send.text(`ā˜€ļø Good morning! ${city_name}: ${temp}°C, ${description}`, chat_id: sub.uid)
}
}
}
note

An on error handler does not stop the flow — execution continues after the call. Without the flow.skip guard, a fetch failure would still run the "Good morning" send with empty values. See Gotchas.

The complete bot​

import openweather from "openweather"

bot WeatherBot {
meta { name: "Weather Bot" slug: "weather-bot" icon: "🌤" }

global openweather_api_key: String = ""
user home_city: String = ""
shared subscribers: Array<{ uid: Number, city: String }> = []

flow start on command "/start" {
send.text("🌤 Weather Bot\n\n/weather — check any city\n/sethome — save your city for a daily forecast")
}

flow weather on command "/weather" {
branch ask.text("Which city?", timeout: "60s") {
"timeout" { send.text("No city given — try /weather again.") stop }
default {
set { flow.city = str.trim(answer) }
let { temp, description, humidity, city_name } = await openweather.current_weather(
city: flow.city, api_key: global.openweather_api_key
) on error {
send.text(`āš ļø Couldn't find weather for "${flow.city}".`)
stop
}
send.text(`🌤 *${city_name}*\n${temp}°C, ${description}\nšŸ’§ Humidity: ${humidity}%`, parse_mode: "Markdown")
}
}
}

flow set_home on command "/sethome" {
branch ask.text("What's your home city?", timeout: "60s") {
"timeout" { send.text("Cancelled.") stop }
default {
set { user.home_city = str.trim(answer) }
set {
shared.subscribers = array.reject(shared.subscribers, "uid", user.id)
shared.subscribers << [{ uid: user.id, city: user.home_city }]
}
send.text(`āœ… Home city set to ${user.home_city}.`)
}
}
}

flow daily_forecast on schedule "0 7 * * *" {
foreach sub in shared.subscribers {
set { flow.skip = false }
let { temp, description, city_name } = await openweather.current_weather(
city: sub.city, api_key: global.openweather_api_key
) on error {
set { flow.skip = true }
send.text("āš ļø Couldn't fetch your forecast today.", chat_id: sub.uid)
}
when !flow.skip {
send.text(`ā˜€ļø Good morning! ${city_name}: ${temp}°C, ${description}`, chat_id: sub.uid)
}
}
}
}

Next​