DeepBotDownload

Quick weather

A card that calls a web API itself through deepbot.net.fetch.

app.json

{
  "format": "deepbot.app/v1",
  "id": "quick-weather",
  "name": "Quick weather",
  "version": "1.0.0",
  "tier": "free",
  "vendor": "DeepBot examples",
  "summary": "The weather for any city as a card in the chat, straight from Open-Meteo.",
  "description": "Ask your Bot about the weather and it shows a card with today and the next days. The card asks Open-Meteo itself, through DeepBot, so the Bot does not have to.",
  "icon": "ui/icon.svg",
  "permissions": {
    "network": ["api.open-meteo.com", "geocoding-api.open-meteo.com"]
  },
  "skill": "skill/SKILL.md",
  "cards": [
    {
      "name": "showQuickWeather",
      "title": "Weather",
      "entry": "ui/card.html",
      "height": 200,
      "description": "Show the weather for a place: today and the next 5 days. Pass `city` (a city name, in any language).",
      "confirmation": "The forecast is on screen. Do not repeat the numbers; answer the person's actual question in a sentence.",
      "parameters": {
        "type": "object",
        "properties": {
          "city": { "type": "string" },
          "units": { "type": "string", "enum": ["celsius", "fahrenheit"] }
        },
        "required": ["city"]
      }
    }
  ]
}

skill/SKILL.md

---
name: quick-weather
description: "Use when the person asks about the weather, temperature, rain or wind somewhere, now or in the next days: shows a weather card from the Quick weather app."
---

# Quick weather

Call `showQuickWeather` with `city` (the place the person named; if they named none, ask which). Use
`units: "fahrenheit"` only when the person uses Fahrenheit.

The card fetches the forecast itself. When it reports what it found, answer the person's real
question in one sentence ("Yes, take an umbrella after 3 pm"). Do not list the numbers again.

ui/card.html

<!doctype html>
<html lang="en">
<head><meta charset="utf-8">
<style>
  .wx { display: grid; gap: 12px; }
  .now { display: flex; align-items: center; gap: 14px; }
  .now .t { font-size: 38px; font-weight: 700; line-height: 1; }
  .days { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
  .day { text-align: center; padding: 8px 4px; border-radius: 10px; background: var(--muted); font-size: 12px; }
  .day b { display: block; font-size: 14px; }
  .ico { font-size: 26px; }
</style>
</head>
<body>
  <div class="db-card wx" id="root"><span class="db-muted">Looking up the weather…</span></div>
  <script src="card.js"></script>
</body>
</html>

ui/card.js

/*
 * A WEATHER CARD. The card itself calls Open-Meteo through deepbot.net.fetch: the frame has no
 * network of its own, DeepBot makes the request, and only to the hosts in permissions.network.
 */
const root = document.getElementById("root");
const ICON = (code) => (code === 0 ? "☀️" : code <= 3 ? "⛅" : code <= 48 ? "🌫️" : code <= 67 ? "🌧️" : code <= 77 ? "❄️" : code <= 82 ? "🌦️" : "⛈️");
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);

async function show() {
	const city = String(deepbot.args.city || "").trim();
	const f = deepbot.args.units === "fahrenheit";
	if (!city) return;
	try {
		// 1. the place -> coordinates
		const geo = await deepbot.net.fetch(
			`https://geocoding-api.open-meteo.com/v1/search?count=1&name=${encodeURIComponent(city)}`,
			{ as: "json" },
		);
		const place = geo.body?.results?.[0];
		if (!place) throw new Error(`No place called "${city}".`);
		// 2. the forecast
		const q = new URLSearchParams({
			latitude: place.latitude,
			longitude: place.longitude,
			current: "temperature_2m,weather_code,wind_speed_10m",
			daily: "weather_code,temperature_2m_max,temperature_2m_min",
			timezone: "auto",
			forecast_days: "5",
			...(f ? { temperature_unit: "fahrenheit" } : {}),
		});
		const wx = await deepbot.net.fetch(`https://api.open-meteo.com/v1/forecast?${q}`, { as: "json" });
		if (!wx.ok) throw new Error(`Open-Meteo answered ${wx.status}.`);
		const c = wx.body.current;
		const d = wx.body.daily;
		const u = f ? "°F" : "°C";
		root.innerHTML = `
			<div class="now"><span class="ico">${ICON(c.weather_code)}</span>
				<div><div class="t">${Math.round(c.temperature_2m)}${u}</div>
				<div class="db-muted">${esc(place.name)}${place.country ? `, ${esc(place.country)}` : ""} · wind ${Math.round(c.wind_speed_10m)} km/h</div></div></div>
			<div class="days">${d.time
				.map(
					(t, i) => `<div class="day">${new Date(t).toLocaleDateString(undefined, { weekday: "short" })}
						<div class="ico">${ICON(d.weather_code[i])}</div>
						<b>${Math.round(d.temperature_2m_max[i])}°</b>${Math.round(d.temperature_2m_min[i])}°</div>`,
				)
				.join("")}</div>`;
		// What the Bot hears back: a short summary it can answer from.
		deepbot.done(`${place.name}: now ${Math.round(c.temperature_2m)}${u}; next days max ${d.temperature_2m_max.map(Math.round).join(", ")}${u}.`);
	} catch (error) {
		root.innerHTML = `<span class="db-muted">${esc(error.message)}</span>`;
		deepbot.fail(error.message);
	}
}

deepbot.ready.then(show);

ui/icon.svg

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><title>Quick weather</title><rect width="64" height="64" rx="16" fill="#3B82F6"/><circle cx="26" cy="26" r="10" fill="#FFD43B"/><path d="M22 46h22a8 8 0 0 0 0-16 11 11 0 0 0-21 3 7 7 0 0 0-1 13z" fill="#fff"/></svg>

listing.md

---
categories: [life]
tags: [weather, forecast, example]
screenshots:
  - The weather card in the chat
---

An example app from the DeepBot docs: a weather card that fetches its own data. Ask your Bot
“will it rain in Lisbon tomorrow?” and it shows today and the next five days.

Data from [Open-Meteo](https://open-meteo.com) (free, no key).

## Changelog

### 1.0.0
- First version.