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)
}
}
}
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)
}
}
}
}