Skip to content

Instantly share code, notes, and snippets.

@sirlancelot
Last active October 22, 2024 22:22
Show Gist options
  • Save sirlancelot/1583689478612435fe9af4ea3990ded2 to your computer and use it in GitHub Desktop.
Save sirlancelot/1583689478612435fe9af4ea3990ded2 to your computer and use it in GitHub Desktop.
Obsidian QuickAdd Macro to fetch the weather from OpenWeatherMap
//@ts-check
/**
* Fetch weather data from OpenWeatherMap API
*
* A free OpenWeatherMap API Key is required and can be entered using the Quick
* Add Macro settings dialog.
*
* You should know how to install and use Quick Add Macros before using this.
* See https://quickadd.obsidian.guide/docs/Choices/MacroChoice
*
* Available variables:
*
* - {{VALUE:clouds}} = `${result.clouds?.all}%`
* - {{VALUE:feelsLike}} = `${feels_like.toFixed(0)}°`
* - {{VALUE:humidity}} = `${humidity}%`
* - {{VALUE:icon}} = parseOpenWeatherMapIcon(weather.icon)
* - {{VALUE:image}} = `https://openweathermap.org/img/wn/${icon}.png`
* - {{VALUE:image2x}} = `https://openweathermap.org/img/wn/${icon}@2x.png`
* - {{VALUE:main}} = weather.main
* - {{VALUE:place}} = `${result.name}`
* - {{VALUE:pressure}} = `${pressure} hPa`
* - {{VALUE:sunrise}} = moment(result.sys.sunrise).format("HH:mm:ss")
* - {{VALUE:sunset}} = moment(result.sys.sunset).format("HH:mm:ss")
* - {{VALUE:temp}} = `${temp.toFixed(0)}°`
* - {{VALUE:tempHigh}} = `${temp_max.toFixed(0)}°`
* - {{VALUE:tempLow}} = `${temp_min.toFixed(0)}°`
* - {{VALUE:wind}} = result.wind
*
* If you like this, please let me know: https://ko-fi.com/sirlancelot 🐱‍👤
*/
/** @type {string} */
const API_BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
/**
* @typedef {Object} Weather
* @property {number} id
* @property {string} main
* @property {string} description
* @property {`${number}${"d"|"n"}`} icon
*/
/**
* @typedef {Object} Main
* @property {number} temp
* @property {number} feels_like
* @property {number} temp_min
* @property {number} temp_max
* @property {number} pressure
* @property {number} humidity
* @property {number} [sea_level]
* @property {number} [grnd_level]
*/
/**
* @typedef {Object} Wind
* @property {number} speed
* @property {number} deg
*/
/**
* @typedef {Object} Clouds
* @property {number} all
*/
/**
* @typedef {Object} Sys
* @property {number} type
* @property {number} id
* @property {string} country
* @property {number} sunrise
* @property {number} sunset
*/
/**
* @typedef {Object} ApiResponse
* @property {{ lat: number; lon: number }} coord
* @property {Weather[]} weather
* @property {string} base
* @property {Main} main
* @property {number} visibility
* @property {Wind} wind
* @property {Clouds} clouds
* @property {number} dt
* @property {Sys} sys
* @property {number} timezone
* @property {number} id
* @property {string} name
* @property {number} cod
* @property {string} message error message
*/
const API_KEY_OPTION = "OpenWeatherMap API Key"
const CITY_OPTION = "City"
const UNITS_OPTION = "Units"
/**
* Convert OpenWeatherMap icon to an emoji character
*
* @see https://openweathermap.org/weather-conditions
* @param {Weather['icon']} icon
*/
function parseOpenWeatherMapIcon(icon) {
return (
{
"01d": "☀️",
"01n": "🌙",
"02d": "⛅",
"02n": "⛅",
"03d": "☁️",
"03n": "☁️",
"04d": "☁️",
"04n": "☁️",
"09d": "🌧️",
"09n": "🌧️",
"10d": "🌦️",
"10n": "🌦️",
"11d": "⛈️",
"11n": "⛈️",
"13d": "❄️",
"13n": "❄️",
"50d": "🌫️",
"50n": "🌫️",
}[icon] || icon
)
}
/**
* @param {{ variables: Record<string, any> }} context
* @param {{ "OpenWeatherMap API Key": string; City: string; Units: string }} settings
*/
async function entry({ variables }, settings) {
const {
[API_KEY_OPTION]: appid,
[CITY_OPTION]: city,
[UNITS_OPTION]: units,
} = settings
if (!appid) throw new Error("No API Key provided.")
if (!city) throw new Error("No City provided.")
if (!units) throw new Error("No Units provided.")
const query = new URLSearchParams({ appid, units })
query.set(Number(city) ? "id" : "q", city)
/** @type {ApiResponse} */
const result = JSON.parse(
// @ts-expect-error - `request` is provided by the environment
await request({
contentType: "application/json",
method: "GET",
throw: false,
url: `${API_BASE_URL}?${query}`,
})
)
if (result.cod !== 200) throw new Error(result.message)
const [weather] = result.weather
const { icon } = weather
const { temp, feels_like, temp_min, temp_max, humidity, pressure } =
result.main
const suffix = units === "imperial" ? "°F" : units === "metric" ? "°C" : "K"
Object.assign(variables, {
clouds: `${result.clouds?.all}%`,
feelsLike: `${feels_like.toFixed(0)}${suffix}`,
humidity: `${humidity}%`,
icon: parseOpenWeatherMapIcon(icon),
image: `https://openweathermap.org/img/wn/${icon}.png`,
image2x: `https://openweathermap.org/img/wn/${icon}@2x.png`,
main: weather.main,
place: `${result.name}`,
pressure: `${pressure} hPa`,
// @ts-expect-error - `moment` is provided by the environment
sunrise: moment(result.sys.sunrise).format("HH:mm:ss"),
// @ts-expect-error - `moment` is provided by the environment
sunset: moment(result.sys.sunset).format("HH:mm:ss"),
temp: `${temp.toFixed(0)}${suffix}`,
tempHigh: `${temp_max.toFixed(0)}${suffix}`,
tempLow: `${temp_min.toFixed(0)}${suffix}`,
wind: result.wind,
})
}
module.exports = {
entry,
settings: {
name: "Weather Retrieval",
author: "Matthew Pietz",
options: {
[API_KEY_OPTION]: {
type: "text",
defaultValue: "",
placeholder: "",
description:
"Visit https://openweathermap.org/ to get an API key",
},
[CITY_OPTION]: {
type: "text",
defaultValue: "Milwaukie, OR, US",
placeholder: "City,State,Country",
description: "Comma separated city name, state code, and country code",
},
[UNITS_OPTION]: {
type: "dropdown",
defaultValue: "imperial",
options: ["imperial", "metric", "standard"],
description:
"Imperial = Fahrenheit, Metric = Celsius, Standard = Kelvin",
},
},
},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment