技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
connector-weatherapi【数据与研究】MANDATORY recipe for every Caffeine build that reads weather data from a canister. The supported path is the `weatherapi-client` mops package (WeatherAPI.com) over outbound HTTPS, authenticated with a query-string API key. Hand-rolling `ic.http_request` calls to `api.weatherapi.com` is a FORBIDDEN anti-pattern — it bypasses the non-replicated-outcall…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
weatherapi-client mops package
(WeatherAPI.com) over outbound HTTPS, authenticated with a query-string API
key. Hand-rolling ic.http_request calls to api.weatherapi.com is a
FORBIDDEN anti-pattern — it bypasses the non-replicated-outcall safeguard
(WeatherAPI bodies carry per-request clocks and fail consensus), the generated
JSON decoding, and the API-key handling. Load this skill whenever the user,
spec, or any prior task mentions weather, forecast, temperature, humidity,
wind, rain, snow, conditions, sunrise, sunset, moon phase, astronomy,
timezone, IP geolocation, marine or tide forecasts, or location autocomplete —
and BEFORE writing any code that touches a weather endpoint.
version: 0.2.1
caffeineai-subscription: [none]
compatibility:
mops:
weatherapi-client: "~0.2.0"weatherapi-clientMotoko bindings for WeatherAPI.com, generated
from its OpenAPI spec. All nine operations live in a single module,
Apis/APIsApi, and all nine are read-only GETs.
A canister that reads the current temperature and a three-day maximum series. Non-replicated is the default, so you only supply the key:
import { realtimeWeather; forecastWeather } "mo:weatherapi-client/Apis/APIsApi";
import { type Config; defaultConfig } "mo:weatherapi-client/Config";
import Array "mo:core/Array"; // in scope so `days.map(…)` dot notation resolves
actor {
// The key is a query-string credential. Hold it in a stable variable set by
// an admin call; never hard-code it in source.
func config(apiKey : Text) : Config = { defaultConfig with auth = ?#apiKey apiKey };
// `q` is any WeatherAPI location query: "Zurich", "47.37,8.55", a postcode,
// an IATA code, or "auto:ip".
public func currentTempC(apiKey : Text, q : Text) : async ?Float {
let res = await* realtimeWeather(config apiKey, q, "");
do ? { res.current!.temp_c! };
};
// Daily maxima for the next three days.
public func maxTempsC(apiKey : Text, q : Text) : async [?Float] {
let res = await* forecastWeather(config apiKey, q, #_3_, "", 0, 0, "", "no", "no", 0);
let ?forecast = res.forecast else return [];
let ?days = forecast.forecastday else return [];
days.map(func(d) = do ? { d.day!.maxtemp_c! });
};
}
| Function | Endpoint | Returns |
|---|---|---|
realtimeWeather(cfg, q, lang) | /current.json | RealtimeWeather200Response |
forecastWeather(cfg, q, days, dt, unixdt, hour, lang, alerts, aqi, tp) | /forecast.json | ForecastWeather200Response |
historyWeather(cfg, q, dt, unixdt, endDt, unixendDt, hour, lang) | /history.json | FutureWeather200Response |
futureWeather(cfg, q, dt, lang) | /future.json | FutureWeather200Response |
marineWeather(cfg, q, days, dt, unixdt, hour, lang) | /marine.json | MarineWeather200Response |
astronomy(cfg, q, dt) | /astronomy.json | Astronomy200Response |
timeZone(cfg, q) | /timezone.json | Location |
ipLookup(cfg, q) | /ip.json | Ip |
searchAutocompleteWeather(cfg, q) | /search.json | [Search] |
days is an enum, not a number: ForecastWeatherDaysParameter is #_1_ … #_14_
and MarineWeatherDaysParameter is #_1_ … #_7_ (the underscores are how the
generator escapes numeric enum values — #_3_, not #_3 or 3).
auth = ?#apiKey key.?key=… (WeatherAPI takes no Authorization
header), so it appears in the request URL. Keep it in a stable variable
written by an admin-only call, and never log the built URL.The package ships is_replicated = ?false in defaultConfig, and that is a
correctness requirement here, not just a cost saving. Every response carries
per-request clocks — Location.localtime / localtime_epoch,
Current.last_updated / last_updated_epoch — which change second to second. A
replicated outcall has every subnet node issue its own request and demands
bit-identical bodies, so those fields would break consensus on most calls while
burning ~13× the cycles. You don't set it yourself; the default is correct.
Override with is_replicated = ?true only together with a transform that
strips the volatile fields.
WeatherAPI marks no response field required, so the generated models are
all-optional: RealtimeWeather200Response.current : ?Current,
Current.temp_c : ?Float, and so on. Reach through them with a do ? block
(do ? { res.current!.temp_c! }) rather than nested switches, and decide what
an absent field means for your caller — the API omits fields your plan does not
cover (for example air_quality without the aqi=yes parameter).
Optional query parameters are dropped when they are "" or 0, because
WeatherAPI rejects empty lang= and zero-valued numerics. So passing "" for
lang, dt, alerts, aqi and 0 for unixdt, tp is how you say "not
supplied" — there is no ?Text parameter to leave null.
One consequence worth knowing: hour = 0 does not select midnight, it omits
the hour filter entirely and you get the whole day's hourly array. Filter the
returned hour : ?[ForecastForecastdayInnerHourInner] yourself if you need
00:00.
Non-2xx responses and decode failures throw Error.reject(…). The client is
generated with diagnostics, so the message is
HTTP <status> body[<n>B]=<first 100 chars>: <reason> — enough to tell a 401
(bad key) from a 403 (endpoint not on your plan) from a 400 (q not resolvable)
without extra logging. Catch with try/catch and surface
Error.message(err).