@happyvertical/weather 0.79.0 → 0.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +1 -1
- package/dist/cli/claude-context.js +9 -9
- package/dist/index.js +910 -1375
- package/metadata.json +2 -2
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,1377 +1,912 @@
|
|
|
1
|
-
import { loadEnvConfig as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
class
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
const n = await fetch(e, { signal: t.signal });
|
|
296
|
-
if (!n.ok)
|
|
297
|
-
throw new u(
|
|
298
|
-
`API request failed: ${n.statusText}`,
|
|
299
|
-
this.name,
|
|
300
|
-
`HTTP_${n.status}`
|
|
301
|
-
);
|
|
302
|
-
return await n.json();
|
|
303
|
-
} finally {
|
|
304
|
-
clearTimeout(i);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Transform Environment Canada data into WeatherForecast objects
|
|
309
|
-
*/
|
|
310
|
-
transformForecasts(e, r, t) {
|
|
311
|
-
if (!e.features || e.features.length === 0)
|
|
312
|
-
throw new w(this.name, r, t);
|
|
313
|
-
const i = [], a = e.features[0].properties, s = a.currentConditions;
|
|
314
|
-
s && i.push({
|
|
315
|
-
timestamp: new Date(s.timestamp?.en || /* @__PURE__ */ new Date()),
|
|
316
|
-
temperature: s.temperature?.value?.en || 0,
|
|
317
|
-
humidity: s.relativeHumidity?.value?.en || 0,
|
|
318
|
-
windSpeed: Number(s.wind?.speed?.value?.en) || 0,
|
|
319
|
-
windDirection: s.wind?.bearing?.value?.en,
|
|
320
|
-
pressure: (s.pressure?.value?.en || 0) * 10,
|
|
321
|
-
// Convert kPa to hPa
|
|
322
|
-
conditions: s.condition?.en || "Unknown",
|
|
323
|
-
confidence: 100,
|
|
324
|
-
// Current conditions are observed
|
|
325
|
-
raw: {
|
|
326
|
-
source: "environment-canada-citypage",
|
|
327
|
-
currentConditions: s
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
const c = a.forecastGroup;
|
|
331
|
-
if (c && c.forecasts) {
|
|
332
|
-
let h = /* @__PURE__ */ new Date();
|
|
333
|
-
for (const l of c.forecasts) {
|
|
334
|
-
const f = l.period, b = l.temperatures?.temperature || [], y = b.find((T) => T.class?.en === "high"), I = b.find((T) => T.class?.en === "low");
|
|
335
|
-
f?.textForecastName?.en?.toLowerCase() === "tonight" || f?.value?.en?.toLowerCase().includes("night") ? h.setHours(18, 0, 0, 0) : h.setHours(6, 0, 0, 0), i.push({
|
|
336
|
-
timestamp: new Date(h),
|
|
337
|
-
temperature: y?.value?.en || I?.value?.en || 0,
|
|
338
|
-
temperatureMax: y?.value?.en,
|
|
339
|
-
temperatureMin: I?.value?.en,
|
|
340
|
-
humidity: l.relativeHumidity?.value?.en || 0,
|
|
341
|
-
windSpeed: Number(l.winds?.periods?.[0]?.speed?.value?.en) || 0,
|
|
342
|
-
windDirection: l.winds?.periods?.[0]?.bearing?.value?.en,
|
|
343
|
-
conditions: l.cloudPrecip?.en || l.textSummary?.en || "Unknown",
|
|
344
|
-
precipAmount: l.precipitation?.accumulation?.amount?.value?.en,
|
|
345
|
-
confidence: 90,
|
|
346
|
-
raw: {
|
|
347
|
-
source: "environment-canada-citypage",
|
|
348
|
-
periodName: f?.textForecastName?.en || f?.value?.en,
|
|
349
|
-
period: l
|
|
350
|
-
}
|
|
351
|
-
}), f?.textForecastName?.en?.toLowerCase().includes("night") ? h = new Date(h.getTime() + 720 * 60 * 1e3) : h = new Date(h.getTime() + 1440 * 60 * 1e3);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return i;
|
|
355
|
-
}
|
|
356
|
-
transformClimateHourly(e, r, t, i) {
|
|
357
|
-
if (!e.features || e.features.length === 0)
|
|
358
|
-
throw new w(this.name, r, t);
|
|
359
|
-
const n = /* @__PURE__ */ new Map();
|
|
360
|
-
for (const s of e.features) {
|
|
361
|
-
const c = s.properties, h = c.UTC_DATE ? U(c.UTC_DATE) : null;
|
|
362
|
-
if (!h || Number.isNaN(h.getTime()))
|
|
363
|
-
continue;
|
|
364
|
-
const l = c.LATITUDE_DECIMAL_DEGREES || s.geometry?.coordinates[1], f = c.LONGITUDE_DECIMAL_DEGREES || s.geometry?.coordinates[0];
|
|
365
|
-
if (typeof l != "number" || typeof f != "number")
|
|
366
|
-
continue;
|
|
367
|
-
const b = typeof c.TEMP == "number" ? c.TEMP : void 0;
|
|
368
|
-
if (b === void 0)
|
|
369
|
-
continue;
|
|
370
|
-
const y = String(
|
|
371
|
-
c.CLIMATE_IDENTIFIER || c.STN_ID || c.STATION_NAME || "nearby"
|
|
372
|
-
), I = x(
|
|
373
|
-
r,
|
|
374
|
-
t,
|
|
375
|
-
l,
|
|
376
|
-
f
|
|
377
|
-
), T = c.WEATHER_ENG_DESC || "Unknown", $ = G(
|
|
378
|
-
c.WIND_DIRECTION
|
|
379
|
-
), L = {
|
|
380
|
-
timestamp: h,
|
|
381
|
-
temperature: m(b),
|
|
382
|
-
feelsLike: typeof c.WINDCHILL == "number" ? m(c.WINDCHILL) : typeof c.HUMIDEX == "number" ? m(c.HUMIDEX) : void 0,
|
|
383
|
-
humidity: m(c.RELATIVE_HUMIDITY || 0),
|
|
384
|
-
windSpeed: m(c.WIND_SPEED || 0),
|
|
385
|
-
windDirection: $,
|
|
386
|
-
pressure: typeof c.STATION_PRESSURE == "number" ? m(c.STATION_PRESSURE * 10) : void 0,
|
|
387
|
-
conditions: T.toUpperCase() === "NA" ? "Unknown" : T,
|
|
388
|
-
visibility: typeof c.VISIBILITY == "number" ? c.VISIBILITY : void 0,
|
|
389
|
-
precipAmount: typeof c.PRECIP_AMOUNT == "number" ? c.PRECIP_AMOUNT : void 0,
|
|
390
|
-
confidence: 100,
|
|
391
|
-
raw: {
|
|
392
|
-
source: "environment-canada-climate-hourly",
|
|
393
|
-
stationId: y,
|
|
394
|
-
stationName: c.STATION_NAME,
|
|
395
|
-
stationLatitude: l,
|
|
396
|
-
stationLongitude: f,
|
|
397
|
-
stationDistanceKm: I,
|
|
398
|
-
observation: c
|
|
399
|
-
}
|
|
400
|
-
}, H = n.get(y);
|
|
401
|
-
H ? H.forecasts.push(L) : n.set(y, { distance: I, forecasts: [L] });
|
|
402
|
-
}
|
|
403
|
-
const a = [...n.values()].map((s) => ({
|
|
404
|
-
distance: s.distance,
|
|
405
|
-
forecasts: i ? R(s.forecasts, i) : s.forecasts.sort(
|
|
406
|
-
(c, h) => c.timestamp.getTime() - h.timestamp.getTime()
|
|
407
|
-
)
|
|
408
|
-
})).filter((s) => s.forecasts.length > 0).sort((s, c) => s.distance - c.distance)[0];
|
|
409
|
-
if (!a)
|
|
410
|
-
throw new w(this.name, r, t);
|
|
411
|
-
return a.forecasts.sort(
|
|
412
|
-
(s, c) => s.timestamp.getTime() - c.timestamp.getTime()
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Test connection to Environment Canada API
|
|
417
|
-
*/
|
|
418
|
-
async testConnection() {
|
|
419
|
-
try {
|
|
420
|
-
const e = this.buildApiUrl(51.0447, -114.0719), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
|
|
421
|
-
return clearTimeout(t), i.ok;
|
|
422
|
-
} catch {
|
|
423
|
-
return !1;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Check if location is in Canada (Environment Canada coverage area)
|
|
428
|
-
*/
|
|
429
|
-
async supportsLocation(e, r) {
|
|
430
|
-
return {
|
|
431
|
-
valid: !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180
|
|
432
|
-
}.valid ? N(e, r) : !1;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
function U(o) {
|
|
436
|
-
const e = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(o);
|
|
437
|
-
return new Date(e ? o : `${o}Z`);
|
|
438
|
-
}
|
|
439
|
-
function G(o) {
|
|
440
|
-
if (typeof o == "number")
|
|
441
|
-
return o <= 36 ? o * 10 : o;
|
|
442
|
-
}
|
|
443
|
-
class q {
|
|
444
|
-
name = "Google Weather";
|
|
445
|
-
providerType = "commercial";
|
|
446
|
-
apiBase = "https://weather.googleapis.com/v1";
|
|
447
|
-
apiKey;
|
|
448
|
-
timeout;
|
|
449
|
-
constructor(e) {
|
|
450
|
-
if (!e.apiKey)
|
|
451
|
-
throw new g("Google Weather", "API key is required");
|
|
452
|
-
this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Fetch weather forecasts for a location
|
|
456
|
-
* Returns combined hourly (240h) and daily (10 days) forecasts
|
|
457
|
-
*/
|
|
458
|
-
async fetchForLocation(e, r, t) {
|
|
459
|
-
p(this.name, e, r);
|
|
460
|
-
try {
|
|
461
|
-
const [i, n] = await Promise.all([
|
|
462
|
-
this.fetchHourlyForecast(e, r, t),
|
|
463
|
-
this.fetchDailyForecast(e, r, t)
|
|
464
|
-
]), a = [...i, ...n];
|
|
465
|
-
return t?.limit && t.limit > 0 ? a.slice(0, t.limit) : a;
|
|
466
|
-
} catch (i) {
|
|
467
|
-
throw i instanceof u ? i : new u(
|
|
468
|
-
`Failed to fetch weather data: ${i.message}`,
|
|
469
|
-
this.name,
|
|
470
|
-
"FETCH_ERROR"
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Fetch current weather conditions
|
|
476
|
-
*/
|
|
477
|
-
async fetchCurrentConditions(e, r, t) {
|
|
478
|
-
p(this.name, e, r);
|
|
479
|
-
const i = this.buildUrl("/currentConditions:lookup", e, r), n = await this.fetchWithTimeout(
|
|
480
|
-
i,
|
|
481
|
-
t?.timeout
|
|
482
|
-
);
|
|
483
|
-
return this.transformCurrentConditions(n);
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Fetch hourly forecast (up to 240 hours)
|
|
487
|
-
*
|
|
488
|
-
* Google Weather API paginates hourly results (24 hours per page).
|
|
489
|
-
* This method automatically fetches all pages to get the full forecast.
|
|
490
|
-
*/
|
|
491
|
-
async fetchHourlyForecast(e, r, t) {
|
|
492
|
-
p(this.name, e, r);
|
|
493
|
-
const i = t?.hours || 240, n = [];
|
|
494
|
-
let a;
|
|
495
|
-
do {
|
|
496
|
-
let s = this.buildUrl(
|
|
497
|
-
"/forecast/hours:lookup",
|
|
498
|
-
e,
|
|
499
|
-
r,
|
|
500
|
-
`&hours=${i}`
|
|
501
|
-
);
|
|
502
|
-
a && (s += `&pageToken=${encodeURIComponent(a)}`);
|
|
503
|
-
const c = await this.fetchWithTimeout(
|
|
504
|
-
s,
|
|
505
|
-
t?.timeout
|
|
506
|
-
);
|
|
507
|
-
c.forecastHours && n.push(...c.forecastHours), a = c.nextPageToken;
|
|
508
|
-
} while (a && n.length < i);
|
|
509
|
-
if (n.length === 0)
|
|
510
|
-
throw new w(this.name, e, r);
|
|
511
|
-
return n.slice(0, i).map((s) => this.transformHourlyForecast(s, "hourly"));
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Fetch daily forecast (up to 10 days)
|
|
515
|
-
*
|
|
516
|
-
* Google Weather API paginates daily results (5 days per page).
|
|
517
|
-
* This method automatically fetches all pages to get the full forecast.
|
|
518
|
-
*/
|
|
519
|
-
async fetchDailyForecast(e, r, t) {
|
|
520
|
-
p(this.name, e, r);
|
|
521
|
-
const i = t?.days || 10, n = [];
|
|
522
|
-
let a;
|
|
523
|
-
do {
|
|
524
|
-
let s = this.buildUrl(
|
|
525
|
-
"/forecast/days:lookup",
|
|
526
|
-
e,
|
|
527
|
-
r,
|
|
528
|
-
`&days=${i}`
|
|
529
|
-
);
|
|
530
|
-
a && (s += `&pageToken=${encodeURIComponent(a)}`);
|
|
531
|
-
const c = await this.fetchWithTimeout(
|
|
532
|
-
s,
|
|
533
|
-
t?.timeout
|
|
534
|
-
);
|
|
535
|
-
c.forecastDays && n.push(...c.forecastDays), a = c.nextPageToken;
|
|
536
|
-
} while (a && n.length < i);
|
|
537
|
-
if (n.length === 0)
|
|
538
|
-
throw new w(this.name, e, r);
|
|
539
|
-
return n.slice(0, i).map((s) => this.transformDailyForecast(s));
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Fetch hourly history (up to 24 hours)
|
|
543
|
-
*/
|
|
544
|
-
async fetchHourlyHistory(e, r, t) {
|
|
545
|
-
p(this.name, e, r);
|
|
546
|
-
const i = t?.hours || 24, n = this.buildUrl(
|
|
547
|
-
"/history/hours:lookup",
|
|
548
|
-
e,
|
|
549
|
-
r,
|
|
550
|
-
`&hours=${i}`
|
|
551
|
-
), a = await this.fetchWithTimeout(
|
|
552
|
-
n,
|
|
553
|
-
t?.timeout
|
|
554
|
-
);
|
|
555
|
-
if (!a.historyHours || a.historyHours.length === 0)
|
|
556
|
-
throw new w(this.name, e, r);
|
|
557
|
-
return a.historyHours.map(
|
|
558
|
-
(s) => this.transformHourlyForecast(s, "history")
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
async fetchHistoricalForLocation(e, r, t) {
|
|
562
|
-
p(this.name, e, r);
|
|
563
|
-
const i = S(this.name, t), n = /* @__PURE__ */ new Date(), a = new Date(n.getTime() - 1440 * 60 * 1e3);
|
|
564
|
-
if (i.end.getTime() > n.getTime())
|
|
565
|
-
throw new u(
|
|
566
|
-
"Google Weather historical lookup cannot end in the future",
|
|
567
|
-
this.name,
|
|
568
|
-
"INVALID_DATE_RANGE"
|
|
569
|
-
);
|
|
570
|
-
if (i.start.getTime() < a.getTime())
|
|
571
|
-
throw new P(
|
|
572
|
-
this.name,
|
|
573
|
-
"historical-weather",
|
|
574
|
-
"Google Weather hourly history is limited to the previous 24 hours."
|
|
575
|
-
);
|
|
576
|
-
const s = Math.min(24, F(i.start, n)), c = await this.fetchHourlyHistory(e, r, {
|
|
577
|
-
...t,
|
|
578
|
-
hours: s
|
|
579
|
-
}), h = R(c, i, t.limit);
|
|
580
|
-
if (h.length === 0)
|
|
581
|
-
throw new w(this.name, e, r);
|
|
582
|
-
return h;
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Fetch weather alerts
|
|
586
|
-
*/
|
|
587
|
-
async fetchAlerts(e, r, t) {
|
|
588
|
-
p(this.name, e, r);
|
|
589
|
-
const i = this.buildUrl("/publicAlerts:lookup", e, r), n = await this.fetchWithTimeout(
|
|
590
|
-
i,
|
|
591
|
-
t?.timeout
|
|
592
|
-
);
|
|
593
|
-
return !n.alerts || n.alerts.length === 0 ? [] : n.alerts.map((a) => this.transformAlert(a));
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Test connection to Google Weather API
|
|
597
|
-
*/
|
|
598
|
-
async testConnection() {
|
|
599
|
-
try {
|
|
600
|
-
const e = this.buildUrl("/currentConditions:lookup", 37.422, -122.084), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
|
|
601
|
-
return clearTimeout(t), i.ok;
|
|
602
|
-
} catch {
|
|
603
|
-
return !1;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Check if location is supported (Google Weather is global)
|
|
608
|
-
*/
|
|
609
|
-
async supportsLocation(e, r) {
|
|
610
|
-
return !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180;
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Build API URL with location and API key
|
|
614
|
-
*/
|
|
615
|
-
buildUrl(e, r, t, i) {
|
|
616
|
-
const n = new URLSearchParams({
|
|
617
|
-
key: this.apiKey,
|
|
618
|
-
"location.latitude": r.toString(),
|
|
619
|
-
"location.longitude": t.toString()
|
|
620
|
-
});
|
|
621
|
-
let a = `${this.apiBase}${e}?${n.toString()}`;
|
|
622
|
-
return i && (a += i), a;
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Fetch with timeout and error handling
|
|
626
|
-
*/
|
|
627
|
-
async fetchWithTimeout(e, r) {
|
|
628
|
-
const t = new AbortController(), i = setTimeout(
|
|
629
|
-
() => t.abort(),
|
|
630
|
-
r || this.timeout
|
|
631
|
-
);
|
|
632
|
-
try {
|
|
633
|
-
const n = await fetch(e, { signal: t.signal });
|
|
634
|
-
return clearTimeout(i), n.ok || await this.handleHttpError(n), await n.json();
|
|
635
|
-
} catch (n) {
|
|
636
|
-
throw clearTimeout(i), n instanceof u ? n : n.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
|
|
637
|
-
`Failed to fetch weather data: ${n.message}`,
|
|
638
|
-
this.name,
|
|
639
|
-
"FETCH_ERROR"
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Handle HTTP error responses
|
|
645
|
-
*/
|
|
646
|
-
async handleHttpError(e) {
|
|
647
|
-
if (e.status === 401 || e.status === 403)
|
|
648
|
-
throw new g(this.name, "Invalid API key");
|
|
649
|
-
if (e.status === 429)
|
|
650
|
-
throw new C(this.name);
|
|
651
|
-
const r = await e.text();
|
|
652
|
-
throw new u(
|
|
653
|
-
`API request failed: ${e.statusText} - ${r}`,
|
|
654
|
-
this.name,
|
|
655
|
-
`HTTP_${e.status}`
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Transform current conditions to WeatherForecast
|
|
660
|
-
*/
|
|
661
|
-
transformCurrentConditions(e) {
|
|
662
|
-
return {
|
|
663
|
-
timestamp: new Date(e.currentTime),
|
|
664
|
-
temperature: m(e.temperature.degrees),
|
|
665
|
-
feelsLike: e.feelsLikeTemperature ? m(e.feelsLikeTemperature.degrees) : void 0,
|
|
666
|
-
conditions: e.weatherCondition.description.text,
|
|
667
|
-
humidity: m(e.relativeHumidity),
|
|
668
|
-
windSpeed: m(d(e.wind.speed.value)),
|
|
669
|
-
windDirection: e.wind.direction.degrees,
|
|
670
|
-
windGust: e.wind.gust ? m(d(e.wind.gust.value)) : void 0,
|
|
671
|
-
pressure: e.airPressure ? m(e.airPressure.meanSeaLevelMillibars) : void 0,
|
|
672
|
-
cloudCover: e.cloudCover,
|
|
673
|
-
visibility: e.visibility ? m(v(e.visibility.distance)) : void 0,
|
|
674
|
-
precipProbability: e.precipitation?.probability?.percent,
|
|
675
|
-
precipAmount: e.precipitation?.qpf?.quantity,
|
|
676
|
-
confidence: 95,
|
|
677
|
-
raw: {
|
|
678
|
-
source: "google-weather-current",
|
|
679
|
-
isDaytime: e.isDaytime,
|
|
680
|
-
uvIndex: e.uvIndex,
|
|
681
|
-
thunderstormProbability: e.thunderstormProbability,
|
|
682
|
-
timeZone: e.timeZone.id
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Transform hourly forecast to WeatherForecast
|
|
688
|
-
*/
|
|
689
|
-
transformHourlyForecast(e, r) {
|
|
690
|
-
return {
|
|
691
|
-
timestamp: new Date(e.interval.startTime),
|
|
692
|
-
temperature: m(e.temperature.degrees),
|
|
693
|
-
feelsLike: e.feelsLikeTemperature ? m(e.feelsLikeTemperature.degrees) : void 0,
|
|
694
|
-
conditions: e.weatherCondition.description.text,
|
|
695
|
-
humidity: m(e.relativeHumidity),
|
|
696
|
-
windSpeed: m(d(e.wind.speed.value)),
|
|
697
|
-
windDirection: e.wind.direction.degrees,
|
|
698
|
-
windGust: e.wind.gust ? m(d(e.wind.gust.value)) : void 0,
|
|
699
|
-
pressure: e.airPressure ? m(e.airPressure.meanSeaLevelMillibars) : void 0,
|
|
700
|
-
cloudCover: e.cloudCover,
|
|
701
|
-
visibility: e.visibility ? m(v(e.visibility.distance)) : void 0,
|
|
702
|
-
precipProbability: e.precipitation?.probability?.percent,
|
|
703
|
-
precipAmount: e.precipitation?.qpf?.quantity,
|
|
704
|
-
confidence: r === "history" ? 100 : 95,
|
|
705
|
-
raw: {
|
|
706
|
-
source: `google-weather-${r}`,
|
|
707
|
-
isDaytime: e.isDaytime,
|
|
708
|
-
uvIndex: e.uvIndex,
|
|
709
|
-
thunderstormProbability: e.thunderstormProbability,
|
|
710
|
-
displayDateTime: e.displayDateTime
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* Transform daily forecast to WeatherForecast
|
|
716
|
-
*/
|
|
717
|
-
transformDailyForecast(e) {
|
|
718
|
-
const r = e.daytimeForecast || e.nighttimeForecast;
|
|
719
|
-
if (!r)
|
|
720
|
-
throw new u(
|
|
721
|
-
"Daily forecast missing both daytime and nighttime data",
|
|
722
|
-
this.name,
|
|
723
|
-
"INVALID_DATA"
|
|
724
|
-
);
|
|
725
|
-
return {
|
|
726
|
-
timestamp: new Date(e.interval.startTime),
|
|
727
|
-
temperature: e.maxTemperature ? m(e.maxTemperature.degrees) : m(r.temperature.degrees),
|
|
728
|
-
temperatureMax: e.maxTemperature ? m(e.maxTemperature.degrees) : void 0,
|
|
729
|
-
temperatureMin: e.minTemperature ? m(e.minTemperature.degrees) : void 0,
|
|
730
|
-
feelsLike: e.feelsLikeMaxTemperature ? m(e.feelsLikeMaxTemperature.degrees) : r.feelsLikeTemperature ? m(r.feelsLikeTemperature.degrees) : void 0,
|
|
731
|
-
conditions: r.weatherCondition.description.text,
|
|
732
|
-
humidity: m(r.relativeHumidity),
|
|
733
|
-
windSpeed: m(
|
|
734
|
-
d(r.wind.speed.value)
|
|
735
|
-
),
|
|
736
|
-
windDirection: r.wind.direction.degrees,
|
|
737
|
-
windGust: r.wind.gust ? m(
|
|
738
|
-
d(r.wind.gust.value)
|
|
739
|
-
) : void 0,
|
|
740
|
-
pressure: r.airPressure ? m(r.airPressure.meanSeaLevelMillibars) : void 0,
|
|
741
|
-
cloudCover: r.cloudCover,
|
|
742
|
-
visibility: r.visibility ? m(v(r.visibility.distance)) : void 0,
|
|
743
|
-
precipProbability: r.precipitation?.probability?.percent,
|
|
744
|
-
precipAmount: r.precipitation?.qpf?.quantity,
|
|
745
|
-
confidence: 90,
|
|
746
|
-
raw: {
|
|
747
|
-
source: "google-weather-daily",
|
|
748
|
-
displayDate: e.displayDate,
|
|
749
|
-
daytimeForecast: e.daytimeForecast,
|
|
750
|
-
nighttimeForecast: e.nighttimeForecast
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Transform alert to WeatherAlert
|
|
756
|
-
*/
|
|
757
|
-
transformAlert(e) {
|
|
758
|
-
return {
|
|
759
|
-
id: e.alertId,
|
|
760
|
-
headline: e.headline || "Weather Alert",
|
|
761
|
-
description: e.description || "",
|
|
762
|
-
severity: e.severity || "unknown",
|
|
763
|
-
startTime: e.effective ? new Date(e.effective) : /* @__PURE__ */ new Date(),
|
|
764
|
-
endTime: e.expires ? new Date(e.expires) : /* @__PURE__ */ new Date(),
|
|
765
|
-
raw: e
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const B = 7200 * 60 * 1e3;
|
|
770
|
-
class V {
|
|
771
|
-
name = "Open-Meteo";
|
|
772
|
-
providerType = "community";
|
|
773
|
-
forecastApiBase = "https://api.open-meteo.com/v1/forecast";
|
|
774
|
-
archiveApiBase = "https://archive-api.open-meteo.com/v1/archive";
|
|
775
|
-
timeout;
|
|
776
|
-
constructor(e = {}) {
|
|
777
|
-
this.timeout = e.timeout || 1e4;
|
|
778
|
-
}
|
|
779
|
-
async fetchForLocation(e, r, t) {
|
|
780
|
-
p(this.name, e, r);
|
|
781
|
-
const i = this.buildBaseParams(e, r);
|
|
782
|
-
i.set("forecast_days", "7");
|
|
783
|
-
const n = await this.fetchJson(
|
|
784
|
-
`${this.forecastApiBase}?${i.toString()}`,
|
|
785
|
-
t?.timeout
|
|
786
|
-
), a = this.transformHourlyData(n, "open-meteo-forecast");
|
|
787
|
-
if (a.length === 0)
|
|
788
|
-
throw new w(this.name, e, r);
|
|
789
|
-
return t?.limit && t.limit > 0 ? a.slice(0, t.limit) : a;
|
|
790
|
-
}
|
|
791
|
-
async fetchHistoricalForLocation(e, r, t) {
|
|
792
|
-
p(this.name, e, r);
|
|
793
|
-
const i = S(this.name, t), n = new Date(Date.now() - B);
|
|
794
|
-
if (i.end.getTime() > n.getTime())
|
|
795
|
-
throw new u(
|
|
796
|
-
"Open-Meteo archive data is published with a 5-7 day lag",
|
|
797
|
-
this.name,
|
|
798
|
-
"INVALID_DATE_RANGE",
|
|
799
|
-
{
|
|
800
|
-
requestedEnd: i.end.toISOString(),
|
|
801
|
-
latestSupportedEnd: n.toISOString()
|
|
802
|
-
}
|
|
803
|
-
);
|
|
804
|
-
const a = this.buildBaseParams(e, r);
|
|
805
|
-
a.set("start_date", _(i.start)), a.set("end_date", _(i.end));
|
|
806
|
-
const s = await this.fetchJson(
|
|
807
|
-
`${this.archiveApiBase}?${a.toString()}`,
|
|
808
|
-
t.timeout
|
|
809
|
-
), c = R(
|
|
810
|
-
this.transformHourlyData(s, "open-meteo-archive"),
|
|
811
|
-
i,
|
|
812
|
-
t.limit
|
|
813
|
-
);
|
|
814
|
-
if (c.length === 0)
|
|
815
|
-
throw new w(this.name, e, r);
|
|
816
|
-
return c;
|
|
817
|
-
}
|
|
818
|
-
async testConnection() {
|
|
819
|
-
try {
|
|
820
|
-
return (await fetch(
|
|
821
|
-
`${this.forecastApiBase}?latitude=51.0447&longitude=-114.0719&hourly=temperature_2m&forecast_days=1&timezone=UTC`
|
|
822
|
-
)).ok;
|
|
823
|
-
} catch {
|
|
824
|
-
return !1;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
async supportsLocation(e, r) {
|
|
828
|
-
return !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180;
|
|
829
|
-
}
|
|
830
|
-
buildBaseParams(e, r) {
|
|
831
|
-
return new URLSearchParams({
|
|
832
|
-
latitude: e.toString(),
|
|
833
|
-
longitude: r.toString(),
|
|
834
|
-
hourly: [
|
|
835
|
-
"temperature_2m",
|
|
836
|
-
"relative_humidity_2m",
|
|
837
|
-
"precipitation",
|
|
838
|
-
"weather_code",
|
|
839
|
-
"cloud_cover",
|
|
840
|
-
"wind_speed_10m",
|
|
841
|
-
"wind_direction_10m",
|
|
842
|
-
"wind_gusts_10m",
|
|
843
|
-
"visibility"
|
|
844
|
-
].join(","),
|
|
845
|
-
temperature_unit: "celsius",
|
|
846
|
-
wind_speed_unit: "kmh",
|
|
847
|
-
precipitation_unit: "mm",
|
|
848
|
-
timezone: "UTC"
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
async fetchJson(e, r) {
|
|
852
|
-
const t = new AbortController(), i = setTimeout(
|
|
853
|
-
() => t.abort(),
|
|
854
|
-
r || this.timeout
|
|
855
|
-
);
|
|
856
|
-
try {
|
|
857
|
-
const n = await fetch(e, { signal: t.signal });
|
|
858
|
-
if (n.status === 429)
|
|
859
|
-
throw new C(this.name);
|
|
860
|
-
if (!n.ok)
|
|
861
|
-
throw new u(
|
|
862
|
-
`API request failed: ${n.statusText}`,
|
|
863
|
-
this.name,
|
|
864
|
-
`HTTP_${n.status}`
|
|
865
|
-
);
|
|
866
|
-
const a = await n.json();
|
|
867
|
-
if (a.error)
|
|
868
|
-
throw new u(
|
|
869
|
-
a.reason || "Open-Meteo API returned an error",
|
|
870
|
-
this.name,
|
|
871
|
-
"API_ERROR",
|
|
872
|
-
a
|
|
873
|
-
);
|
|
874
|
-
return a;
|
|
875
|
-
} catch (n) {
|
|
876
|
-
throw n instanceof u ? n : n.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
|
|
877
|
-
`Failed to fetch weather data: ${n.message}`,
|
|
878
|
-
this.name,
|
|
879
|
-
"FETCH_ERROR"
|
|
880
|
-
);
|
|
881
|
-
} finally {
|
|
882
|
-
clearTimeout(i);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
transformHourlyData(e, r) {
|
|
886
|
-
const t = e.hourly;
|
|
887
|
-
if (!t?.time?.length)
|
|
888
|
-
return [];
|
|
889
|
-
const i = [];
|
|
890
|
-
for (let n = 0; n < t.time.length; n++) {
|
|
891
|
-
const a = z(t.time[n]), s = t.temperature_2m?.[n], c = E(t.relative_humidity_2m?.[n]), h = E(t.wind_speed_10m?.[n]);
|
|
892
|
-
if (Number.isNaN(a.getTime()) || typeof s != "number" || c === void 0 || h === void 0)
|
|
893
|
-
continue;
|
|
894
|
-
const l = t.weather_code?.[n];
|
|
895
|
-
i.push({
|
|
896
|
-
timestamp: a,
|
|
897
|
-
temperature: m(s),
|
|
898
|
-
humidity: m(c),
|
|
899
|
-
windSpeed: m(h),
|
|
900
|
-
windDirection: E(t.wind_direction_10m?.[n]),
|
|
901
|
-
windGust: typeof t.wind_gusts_10m?.[n] == "number" ? m(t.wind_gusts_10m[n]) : void 0,
|
|
902
|
-
conditions: Y(l),
|
|
903
|
-
cloudCover: E(t.cloud_cover?.[n]),
|
|
904
|
-
visibility: typeof t.visibility?.[n] == "number" ? m(v(t.visibility[n])) : void 0,
|
|
905
|
-
precipAmount: E(t.precipitation?.[n]),
|
|
906
|
-
confidence: r === "open-meteo-archive" ? 100 : 90,
|
|
907
|
-
raw: {
|
|
908
|
-
source: r,
|
|
909
|
-
weatherCode: l,
|
|
910
|
-
latitude: e.latitude,
|
|
911
|
-
longitude: e.longitude,
|
|
912
|
-
timezone: e.timezone,
|
|
913
|
-
units: e.hourly_units
|
|
914
|
-
}
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
return i.sort(
|
|
918
|
-
(n, a) => n.timestamp.getTime() - a.timestamp.getTime()
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
function E(o) {
|
|
923
|
-
return typeof o == "number" ? o : void 0;
|
|
924
|
-
}
|
|
925
|
-
function z(o) {
|
|
926
|
-
const e = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(o);
|
|
927
|
-
return new Date(e ? o : `${o}Z`);
|
|
928
|
-
}
|
|
929
|
-
function Y(o) {
|
|
930
|
-
switch (o) {
|
|
931
|
-
case 0:
|
|
932
|
-
return "Clear sky";
|
|
933
|
-
case 1:
|
|
934
|
-
return "Mainly clear";
|
|
935
|
-
case 2:
|
|
936
|
-
return "Partly cloudy";
|
|
937
|
-
case 3:
|
|
938
|
-
return "Overcast";
|
|
939
|
-
case 45:
|
|
940
|
-
case 48:
|
|
941
|
-
return "Fog";
|
|
942
|
-
case 51:
|
|
943
|
-
case 53:
|
|
944
|
-
case 55:
|
|
945
|
-
return "Drizzle";
|
|
946
|
-
case 56:
|
|
947
|
-
case 57:
|
|
948
|
-
return "Freezing drizzle";
|
|
949
|
-
case 61:
|
|
950
|
-
case 63:
|
|
951
|
-
case 65:
|
|
952
|
-
return "Rain";
|
|
953
|
-
case 66:
|
|
954
|
-
case 67:
|
|
955
|
-
return "Freezing rain";
|
|
956
|
-
case 71:
|
|
957
|
-
case 73:
|
|
958
|
-
case 75:
|
|
959
|
-
return "Snow";
|
|
960
|
-
case 77:
|
|
961
|
-
return "Snow grains";
|
|
962
|
-
case 80:
|
|
963
|
-
case 81:
|
|
964
|
-
case 82:
|
|
965
|
-
return "Rain showers";
|
|
966
|
-
case 85:
|
|
967
|
-
case 86:
|
|
968
|
-
return "Snow showers";
|
|
969
|
-
case 95:
|
|
970
|
-
return "Thunderstorm";
|
|
971
|
-
case 96:
|
|
972
|
-
case 99:
|
|
973
|
-
return "Thunderstorm with hail";
|
|
974
|
-
default:
|
|
975
|
-
return "Unknown";
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
class j {
|
|
979
|
-
name = "OpenWeatherMap";
|
|
980
|
-
providerType = "commercial";
|
|
981
|
-
apiBase = "https://api.openweathermap.org/data/2.5/forecast";
|
|
982
|
-
apiKey;
|
|
983
|
-
timeout;
|
|
984
|
-
constructor(e) {
|
|
985
|
-
if (!e.apiKey)
|
|
986
|
-
throw new g("OpenWeatherMap", "API key is required");
|
|
987
|
-
this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
|
|
988
|
-
}
|
|
989
|
-
/**
|
|
990
|
-
* Fetch weather forecasts for a location
|
|
991
|
-
*/
|
|
992
|
-
async fetchForLocation(e, r, t) {
|
|
993
|
-
p(this.name, e, r);
|
|
994
|
-
try {
|
|
995
|
-
const i = this.buildApiUrl(e, r), n = new AbortController(), a = setTimeout(
|
|
996
|
-
() => n.abort(),
|
|
997
|
-
t?.timeout || this.timeout
|
|
998
|
-
), s = await fetch(i, { signal: n.signal });
|
|
999
|
-
if (clearTimeout(a), !s.ok)
|
|
1000
|
-
throw s.status === 401 || s.status === 403 ? new g(this.name, "Invalid API key") : s.status === 429 ? new C(this.name) : new u(
|
|
1001
|
-
`API request failed: ${s.statusText}`,
|
|
1002
|
-
this.name,
|
|
1003
|
-
`HTTP_${s.status}`
|
|
1004
|
-
);
|
|
1005
|
-
const c = await s.json(), h = this.transformForecasts(c);
|
|
1006
|
-
return t?.limit && t.limit > 0 ? h.slice(0, t.limit) : h;
|
|
1007
|
-
} catch (i) {
|
|
1008
|
-
throw i instanceof u ? i : i.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
|
|
1009
|
-
`Failed to fetch weather data: ${i.message}`,
|
|
1010
|
-
this.name,
|
|
1011
|
-
"FETCH_ERROR"
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
async fetchHistoricalForLocation(e, r, t) {
|
|
1016
|
-
throw p(this.name, e, r), new P(
|
|
1017
|
-
this.name,
|
|
1018
|
-
"historical-weather",
|
|
1019
|
-
"The OpenWeatherMap forecast adapter does not expose OpenWeatherMap historical products yet."
|
|
1020
|
-
);
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Build the API URL with coordinates and API key
|
|
1024
|
-
*/
|
|
1025
|
-
buildApiUrl(e, r) {
|
|
1026
|
-
const t = new URLSearchParams({
|
|
1027
|
-
lat: e.toString(),
|
|
1028
|
-
lon: r.toString(),
|
|
1029
|
-
appid: this.apiKey,
|
|
1030
|
-
units: "metric",
|
|
1031
|
-
// Celsius
|
|
1032
|
-
cnt: "40"
|
|
1033
|
-
// Get all 40 forecast periods (5 days)
|
|
1034
|
-
});
|
|
1035
|
-
return `${this.apiBase}?${t.toString()}`;
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Transform OpenWeatherMap API data to WeatherForecast objects
|
|
1039
|
-
*/
|
|
1040
|
-
transformForecasts(e) {
|
|
1041
|
-
const r = [];
|
|
1042
|
-
for (const t of e.list) {
|
|
1043
|
-
const i = new Date(t.dt * 1e3);
|
|
1044
|
-
r.push({
|
|
1045
|
-
timestamp: i,
|
|
1046
|
-
temperature: m(t.main.temp),
|
|
1047
|
-
feelsLike: m(t.main.feels_like),
|
|
1048
|
-
temperatureMin: m(t.main.temp_min),
|
|
1049
|
-
temperatureMax: m(t.main.temp_max),
|
|
1050
|
-
pressure: m(t.main.pressure),
|
|
1051
|
-
humidity: m(t.main.humidity),
|
|
1052
|
-
windSpeed: m(d(t.wind.speed)),
|
|
1053
|
-
windDirection: t.wind.deg,
|
|
1054
|
-
windGust: t.wind.gust ? m(d(t.wind.gust)) : void 0,
|
|
1055
|
-
conditions: t.weather[0]?.description || "Unknown",
|
|
1056
|
-
cloudCover: t.clouds.all,
|
|
1057
|
-
visibility: t.visibility ? m(v(t.visibility)) : void 0,
|
|
1058
|
-
precipProbability: m(t.pop * 100),
|
|
1059
|
-
precipAmount: t.rain?.["3h"] || t.snow?.["3h"] || 0,
|
|
1060
|
-
confidence: 95,
|
|
1061
|
-
raw: {
|
|
1062
|
-
source: "openweathermap-5day-3hour",
|
|
1063
|
-
timestamp: t.dt,
|
|
1064
|
-
weather: t.weather,
|
|
1065
|
-
partOfDay: t.sys.pod,
|
|
1066
|
-
cityData: {
|
|
1067
|
-
name: e.city.name,
|
|
1068
|
-
country: e.city.country,
|
|
1069
|
-
timezone: e.city.timezone
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
return r;
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Test connection to OpenWeatherMap API
|
|
1078
|
-
*/
|
|
1079
|
-
async testConnection() {
|
|
1080
|
-
try {
|
|
1081
|
-
const e = this.buildApiUrl(51.0447, -114.0719), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
|
|
1082
|
-
return clearTimeout(t), i.ok;
|
|
1083
|
-
} catch {
|
|
1084
|
-
return !1;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
/**
|
|
1088
|
-
* Check if location is supported (OpenWeatherMap is global)
|
|
1089
|
-
*/
|
|
1090
|
-
async supportsLocation(e, r) {
|
|
1091
|
-
return {
|
|
1092
|
-
valid: !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180
|
|
1093
|
-
}.valid;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
class Z {
|
|
1097
|
-
name = "OpenWeatherMap One Call";
|
|
1098
|
-
providerType = "commercial";
|
|
1099
|
-
apiBase = "https://api.openweathermap.org/data/3.0/onecall";
|
|
1100
|
-
apiKey;
|
|
1101
|
-
timeout;
|
|
1102
|
-
constructor(e) {
|
|
1103
|
-
if (!e.apiKey)
|
|
1104
|
-
throw new g(
|
|
1105
|
-
"OpenWeatherMap One Call",
|
|
1106
|
-
"API key is required"
|
|
1107
|
-
);
|
|
1108
|
-
this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
|
|
1109
|
-
}
|
|
1110
|
-
/**
|
|
1111
|
-
* Fetch weather forecasts for a location
|
|
1112
|
-
*/
|
|
1113
|
-
async fetchForLocation(e, r, t) {
|
|
1114
|
-
p(this.name, e, r);
|
|
1115
|
-
try {
|
|
1116
|
-
const i = this.buildApiUrl(e, r), n = new AbortController(), a = setTimeout(
|
|
1117
|
-
() => n.abort(),
|
|
1118
|
-
t?.timeout || this.timeout
|
|
1119
|
-
), s = await fetch(i, { signal: n.signal });
|
|
1120
|
-
if (clearTimeout(a), !s.ok) {
|
|
1121
|
-
if (s.status === 401 || s.status === 403)
|
|
1122
|
-
throw new g(this.name, "Invalid API key");
|
|
1123
|
-
if (s.status === 429)
|
|
1124
|
-
throw new C(this.name);
|
|
1125
|
-
const l = await s.text();
|
|
1126
|
-
throw new u(
|
|
1127
|
-
`API request failed: ${s.statusText} - ${l}`,
|
|
1128
|
-
this.name,
|
|
1129
|
-
`HTTP_${s.status}`
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
const c = await s.json(), h = this.transformForecasts(c);
|
|
1133
|
-
return t?.limit && t.limit > 0 ? h.slice(0, t.limit) : h;
|
|
1134
|
-
} catch (i) {
|
|
1135
|
-
throw i instanceof u ? i : i.name === "AbortError" ? new u("Request timeout", this.name, "TIMEOUT") : new u(
|
|
1136
|
-
`Failed to fetch weather data: ${i.message}`,
|
|
1137
|
-
this.name,
|
|
1138
|
-
"FETCH_ERROR"
|
|
1139
|
-
);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
async fetchHistoricalForLocation(e, r, t) {
|
|
1143
|
-
throw p(this.name, e, r), new P(
|
|
1144
|
-
this.name,
|
|
1145
|
-
"historical-weather",
|
|
1146
|
-
"OpenWeatherMap One Call historical data is not exposed through the standardized adapter yet."
|
|
1147
|
-
);
|
|
1148
|
-
}
|
|
1149
|
-
/**
|
|
1150
|
-
* Build the API URL with coordinates and API key
|
|
1151
|
-
*/
|
|
1152
|
-
buildApiUrl(e, r) {
|
|
1153
|
-
const t = new URLSearchParams({
|
|
1154
|
-
lat: e.toString(),
|
|
1155
|
-
lon: r.toString(),
|
|
1156
|
-
appid: this.apiKey,
|
|
1157
|
-
units: "metric",
|
|
1158
|
-
// Celsius
|
|
1159
|
-
exclude: "minutely,alerts"
|
|
1160
|
-
// We only want current, hourly, and daily
|
|
1161
|
-
});
|
|
1162
|
-
return `${this.apiBase}?${t.toString()}`;
|
|
1163
|
-
}
|
|
1164
|
-
/**
|
|
1165
|
-
* Transform OpenWeatherMap One Call API data to WeatherForecast objects
|
|
1166
|
-
*/
|
|
1167
|
-
transformForecasts(e) {
|
|
1168
|
-
const r = [];
|
|
1169
|
-
if (e.hourly)
|
|
1170
|
-
for (const t of e.hourly) {
|
|
1171
|
-
const i = new Date(t.dt * 1e3);
|
|
1172
|
-
r.push({
|
|
1173
|
-
timestamp: i,
|
|
1174
|
-
temperature: m(t.temp),
|
|
1175
|
-
feelsLike: m(t.feels_like),
|
|
1176
|
-
pressure: m(t.pressure),
|
|
1177
|
-
humidity: m(t.humidity),
|
|
1178
|
-
windSpeed: m(d(t.wind_speed)),
|
|
1179
|
-
windDirection: t.wind_deg,
|
|
1180
|
-
windGust: t.wind_gust ? m(d(t.wind_gust)) : void 0,
|
|
1181
|
-
conditions: t.weather[0]?.description || "Unknown",
|
|
1182
|
-
cloudCover: t.clouds,
|
|
1183
|
-
visibility: t.visibility ? m(v(t.visibility)) : void 0,
|
|
1184
|
-
precipProbability: m(t.pop * 100),
|
|
1185
|
-
precipAmount: t.rain?.["1h"] || t.snow?.["1h"] || 0,
|
|
1186
|
-
confidence: 95,
|
|
1187
|
-
raw: {
|
|
1188
|
-
source: "openweathermap-onecall-hourly",
|
|
1189
|
-
timestamp: t.dt,
|
|
1190
|
-
weather: t.weather
|
|
1191
|
-
}
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
if (e.daily)
|
|
1195
|
-
for (const t of e.daily) {
|
|
1196
|
-
const i = new Date(t.dt * 1e3);
|
|
1197
|
-
r.push({
|
|
1198
|
-
timestamp: i,
|
|
1199
|
-
temperature: m(t.temp.day),
|
|
1200
|
-
temperatureMax: m(t.temp.max),
|
|
1201
|
-
temperatureMin: m(t.temp.min),
|
|
1202
|
-
feelsLike: m(t.feels_like.day),
|
|
1203
|
-
pressure: m(t.pressure),
|
|
1204
|
-
humidity: m(t.humidity),
|
|
1205
|
-
windSpeed: m(d(t.wind_speed)),
|
|
1206
|
-
windDirection: t.wind_deg,
|
|
1207
|
-
windGust: t.wind_gust ? m(d(t.wind_gust)) : void 0,
|
|
1208
|
-
conditions: t.weather[0]?.description || "Unknown",
|
|
1209
|
-
cloudCover: t.clouds,
|
|
1210
|
-
precipProbability: m(t.pop * 100),
|
|
1211
|
-
precipAmount: t.rain || t.snow || 0,
|
|
1212
|
-
confidence: 95,
|
|
1213
|
-
raw: {
|
|
1214
|
-
source: "openweathermap-onecall-daily",
|
|
1215
|
-
timestamp: t.dt,
|
|
1216
|
-
weather: t.weather,
|
|
1217
|
-
sunrise: t.sunrise,
|
|
1218
|
-
sunset: t.sunset,
|
|
1219
|
-
uvi: t.uvi
|
|
1220
|
-
}
|
|
1221
|
-
});
|
|
1222
|
-
}
|
|
1223
|
-
return r;
|
|
1224
|
-
}
|
|
1225
|
-
/**
|
|
1226
|
-
* Test connection to OpenWeatherMap One Call API
|
|
1227
|
-
*/
|
|
1228
|
-
async testConnection() {
|
|
1229
|
-
try {
|
|
1230
|
-
const e = this.buildApiUrl(51.0447, -114.0719), r = new AbortController(), t = setTimeout(() => r.abort(), this.timeout), i = await fetch(e, { signal: r.signal });
|
|
1231
|
-
return clearTimeout(t), i.ok;
|
|
1232
|
-
} catch {
|
|
1233
|
-
return !1;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
/**
|
|
1237
|
-
* Check if location is supported (One Call API is global)
|
|
1238
|
-
*/
|
|
1239
|
-
async supportsLocation(e, r) {
|
|
1240
|
-
return {
|
|
1241
|
-
valid: !Number.isNaN(e) && !Number.isNaN(r) && e >= -90 && e <= 90 && r >= -180 && r <= 180
|
|
1242
|
-
}.valid;
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
const J = {
|
|
1246
|
-
// Provider selection
|
|
1247
|
-
provider: "HAVE_WEATHER_PROVIDER",
|
|
1248
|
-
// OpenWeatherMap API key
|
|
1249
|
-
apiKey: "OPENWEATHER_API_KEY",
|
|
1250
|
-
// Google API key
|
|
1251
|
-
googleApiKey: "GOOGLE_API_KEY",
|
|
1252
|
-
// Timeouts
|
|
1253
|
-
timeout: "HAVE_WEATHER_TIMEOUT"
|
|
1
|
+
import { loadEnvConfig as e } from "@happyvertical/utils";
|
|
2
|
+
//#region src/shared/types.ts
|
|
3
|
+
var t = class extends Error {
|
|
4
|
+
provider;
|
|
5
|
+
code;
|
|
6
|
+
details;
|
|
7
|
+
constructor(e, t, n, r) {
|
|
8
|
+
super(e), this.provider = t, this.code = n, this.details = r, this.name = "WeatherError";
|
|
9
|
+
}
|
|
10
|
+
}, n = class extends t {
|
|
11
|
+
constructor(e, t) {
|
|
12
|
+
super(`Rate limit exceeded for ${e}${t ? `. Retry after ${t}s` : ""}`, e, "RATE_LIMIT"), this.name = "RateLimitError";
|
|
13
|
+
}
|
|
14
|
+
}, r = class extends t {
|
|
15
|
+
constructor(e, t) {
|
|
16
|
+
super(t || `Authentication failed for ${e}`, e, "AUTH_ERROR"), this.name = "AuthenticationError";
|
|
17
|
+
}
|
|
18
|
+
}, i = class extends t {
|
|
19
|
+
constructor(e, t, n, r) {
|
|
20
|
+
super(r || `Location (${t}, ${n}) is invalid or unsupported`, e, "INVALID_LOCATION", {
|
|
21
|
+
latitude: t,
|
|
22
|
+
longitude: n
|
|
23
|
+
}), this.name = "InvalidLocationError";
|
|
24
|
+
}
|
|
25
|
+
}, a = class extends t {
|
|
26
|
+
constructor(e, t, n) {
|
|
27
|
+
super(`No forecast data available for location (${t}, ${n})`, e, "NO_RESULTS", {
|
|
28
|
+
latitude: t,
|
|
29
|
+
longitude: n
|
|
30
|
+
}), this.name = "NoResultsError";
|
|
31
|
+
}
|
|
32
|
+
}, o = class extends t {
|
|
33
|
+
constructor(e, t, n) {
|
|
34
|
+
super(n || `${e} does not support weather capability: ${t}`, e, "UNSUPPORTED_CAPABILITY", { capability: t }), this.name = "UnsupportedWeatherCapabilityError";
|
|
35
|
+
}
|
|
36
|
+
}, s = 3600 * 1e3;
|
|
37
|
+
function c(e, n) {
|
|
38
|
+
let r = n.interval || "hourly";
|
|
39
|
+
if (r !== "hourly") throw new t(`Unsupported historical weather interval: ${r}`, e, "UNSUPPORTED_INTERVAL", { interval: r });
|
|
40
|
+
let i = f(e, n.start, "start"), a = f(e, n.end || n.start, "end");
|
|
41
|
+
if (a.getTime() < i.getTime()) throw new t("Historical weather end time must be after start time", e, "INVALID_DATE_RANGE", {
|
|
42
|
+
start: i.toISOString(),
|
|
43
|
+
end: a.toISOString()
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
start: i,
|
|
47
|
+
end: a
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function l(e, t, n) {
|
|
51
|
+
let r = e.filter((e) => e.timestamp.getTime() >= t.start.getTime() && e.timestamp.getTime() <= t.end.getTime()).sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime());
|
|
52
|
+
return n && n > 0 ? r.slice(0, n) : r;
|
|
53
|
+
}
|
|
54
|
+
function u(e, t) {
|
|
55
|
+
return Math.max(1, Math.ceil((t.getTime() - e.getTime()) / s) + 1);
|
|
56
|
+
}
|
|
57
|
+
function d(e) {
|
|
58
|
+
return e.toISOString().slice(0, 10);
|
|
59
|
+
}
|
|
60
|
+
function f(e, n, r) {
|
|
61
|
+
let i = n instanceof Date ? n : new Date(n);
|
|
62
|
+
if (Number.isNaN(i.getTime())) throw new t(`Invalid historical weather ${r} time`, e, "INVALID_DATE_RANGE", { [r]: n });
|
|
63
|
+
return i;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/shared/utils.ts
|
|
67
|
+
function p(e, t) {
|
|
68
|
+
return typeof e != "number" || typeof t != "number" ? {
|
|
69
|
+
valid: !1,
|
|
70
|
+
error: "Coordinates must be numbers"
|
|
71
|
+
} : Number.isNaN(e) || Number.isNaN(t) ? {
|
|
72
|
+
valid: !1,
|
|
73
|
+
error: "Coordinates cannot be NaN"
|
|
74
|
+
} : e < -90 || e > 90 ? {
|
|
75
|
+
valid: !1,
|
|
76
|
+
error: `Invalid latitude: ${e} (must be between -90 and 90)`
|
|
77
|
+
} : t < -180 || t > 180 ? {
|
|
78
|
+
valid: !1,
|
|
79
|
+
error: `Invalid longitude: ${t} (must be between -180 and 180)`
|
|
80
|
+
} : { valid: !0 };
|
|
81
|
+
}
|
|
82
|
+
function m(e, t, n) {
|
|
83
|
+
let r = p(t, n);
|
|
84
|
+
if (!r.valid) throw new i(e, t, n, r.error);
|
|
85
|
+
}
|
|
86
|
+
function h(e) {
|
|
87
|
+
return e - 273.15;
|
|
88
|
+
}
|
|
89
|
+
function g(e) {
|
|
90
|
+
return (e - 32) * 5 / 9;
|
|
91
|
+
}
|
|
92
|
+
function _(e) {
|
|
93
|
+
return e * 3.6;
|
|
94
|
+
}
|
|
95
|
+
function v(e) {
|
|
96
|
+
return e * 1.60934;
|
|
97
|
+
}
|
|
98
|
+
function y(e) {
|
|
99
|
+
return e / 1e3;
|
|
100
|
+
}
|
|
101
|
+
function b(e) {
|
|
102
|
+
return e * 1.60934;
|
|
103
|
+
}
|
|
104
|
+
function x(e) {
|
|
105
|
+
return Math.round(e);
|
|
106
|
+
}
|
|
107
|
+
function S(e, t) {
|
|
108
|
+
return e >= 41.7 && e <= 83.1 && t >= -141 && t <= -52.6;
|
|
109
|
+
}
|
|
110
|
+
function C(e, t, n, r) {
|
|
111
|
+
let i = w(n - e), a = w(r - t), o = Math.sin(i / 2) * Math.sin(i / 2) + Math.cos(w(e)) * Math.cos(w(n)) * Math.sin(a / 2) * Math.sin(a / 2);
|
|
112
|
+
return 6371 * (2 * Math.atan2(Math.sqrt(o), Math.sqrt(1 - o)));
|
|
113
|
+
}
|
|
114
|
+
function w(e) {
|
|
115
|
+
return e * Math.PI / 180;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/providers/environment-canada.ts
|
|
119
|
+
var T = class {
|
|
120
|
+
name = "Environment Canada";
|
|
121
|
+
providerType = "government";
|
|
122
|
+
apiBase = "https://api.weather.gc.ca/collections/citypageweather-realtime/items";
|
|
123
|
+
climateHourlyApiBase = "https://api.weather.gc.ca/collections/climate-hourly/items";
|
|
124
|
+
timeout;
|
|
125
|
+
constructor(e = {}) {
|
|
126
|
+
this.timeout = e.timeout || 1e4;
|
|
127
|
+
}
|
|
128
|
+
async fetchForLocation(e, n, r) {
|
|
129
|
+
if (m(this.name, e, n), !S(e, n)) throw new i(this.name, e, n, "Environment Canada only supports Canadian locations");
|
|
130
|
+
try {
|
|
131
|
+
let i = this.buildApiUrl(e, n), a = new AbortController(), o = setTimeout(() => a.abort(), r?.timeout || this.timeout), s = await fetch(i, { signal: a.signal });
|
|
132
|
+
if (clearTimeout(o), !s.ok) throw new t(`API request failed: ${s.statusText}`, this.name, `HTTP_${s.status}`);
|
|
133
|
+
let c = await s.json(), l = this.transformForecasts(c, e, n);
|
|
134
|
+
return r?.limit && r.limit > 0 ? l.slice(0, r.limit) : l;
|
|
135
|
+
} catch (e) {
|
|
136
|
+
throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async fetchHistoricalForLocation(e, n, r) {
|
|
140
|
+
if (m(this.name, e, n), !S(e, n)) throw new i(this.name, e, n, "Environment Canada historical climate observations only support Canadian locations");
|
|
141
|
+
let o = c(this.name, r);
|
|
142
|
+
try {
|
|
143
|
+
let t = this.buildClimateHourlyUrl(e, n, o), i = await this.fetchClimateHourlyPages(t, o, r.timeout), s = this.transformClimateHourly(i, e, n, o), c = r.limit && r.limit > 0 ? s.slice(0, r.limit) : s;
|
|
144
|
+
if (c.length === 0) throw new a(this.name, e, n);
|
|
145
|
+
return c;
|
|
146
|
+
} catch (e) {
|
|
147
|
+
throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch historical weather data: ${e.message}`, this.name, "FETCH_ERROR");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
buildApiUrl(e, t) {
|
|
151
|
+
let n = new URLSearchParams({
|
|
152
|
+
f: "json",
|
|
153
|
+
bbox: `${t - 1},${e - 1},${t + 1},${e + 1}`,
|
|
154
|
+
limit: "1"
|
|
155
|
+
});
|
|
156
|
+
return `${this.apiBase}?${n.toString()}`;
|
|
157
|
+
}
|
|
158
|
+
buildClimateHourlyUrl(e, t, n) {
|
|
159
|
+
let r = /* @__PURE__ */ new Date(n.start.getTime() - 1440 * 60 * 1e3), i = new Date(n.end.getTime() + 1440 * 60 * 1e3), a = new URLSearchParams({
|
|
160
|
+
f: "json",
|
|
161
|
+
bbox: `${t - 1},${e - 1},${t + 1},${e + 1}`,
|
|
162
|
+
datetime: `${d(r)}/${d(i)}`,
|
|
163
|
+
limit: "2000"
|
|
164
|
+
});
|
|
165
|
+
return `${this.climateHourlyApiBase}?${a.toString()}`;
|
|
166
|
+
}
|
|
167
|
+
async fetchClimateHourlyPages(e, n, r) {
|
|
168
|
+
let i = [], a = /* @__PURE__ */ new Set(), o = e, s = 0;
|
|
169
|
+
for (; o;) {
|
|
170
|
+
if (a.has(o)) throw new t("Environment Canada climate pagination loop detected", this.name, "PAGINATION_LOOP");
|
|
171
|
+
if (a.add(o), s += 1, s > 50) throw new t("Environment Canada climate pagination exceeded safety limit", this.name, "PAGINATION_LIMIT");
|
|
172
|
+
let e = await this.fetchClimateHourlyPage(o, r);
|
|
173
|
+
i.push(...(e.features || []).filter((e) => this.isClimateFeatureInWindow(e, n))), o = e.links?.find((e) => e.rel === "next" && e.href)?.href;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
type: "FeatureCollection",
|
|
177
|
+
features: i
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
isClimateFeatureInWindow(e, t) {
|
|
181
|
+
let n = e.properties.UTC_DATE;
|
|
182
|
+
if (!n) return !1;
|
|
183
|
+
let r = E(n);
|
|
184
|
+
return !Number.isNaN(r.getTime()) && r.getTime() >= t.start.getTime() && r.getTime() <= t.end.getTime();
|
|
185
|
+
}
|
|
186
|
+
async fetchClimateHourlyPage(e, n) {
|
|
187
|
+
let r = new AbortController(), i = setTimeout(() => r.abort(), n || this.timeout);
|
|
188
|
+
try {
|
|
189
|
+
let n = await fetch(e, { signal: r.signal });
|
|
190
|
+
if (!n.ok) throw new t(`API request failed: ${n.statusText}`, this.name, `HTTP_${n.status}`);
|
|
191
|
+
return await n.json();
|
|
192
|
+
} finally {
|
|
193
|
+
clearTimeout(i);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
transformForecasts(e, t, n) {
|
|
197
|
+
if (!e.features || e.features.length === 0) throw new a(this.name, t, n);
|
|
198
|
+
let r = [], i = e.features[0].properties, o = i.currentConditions;
|
|
199
|
+
o && r.push({
|
|
200
|
+
timestamp: new Date(o.timestamp?.en || /* @__PURE__ */ new Date()),
|
|
201
|
+
temperature: o.temperature?.value?.en || 0,
|
|
202
|
+
humidity: o.relativeHumidity?.value?.en || 0,
|
|
203
|
+
windSpeed: Number(o.wind?.speed?.value?.en) || 0,
|
|
204
|
+
windDirection: o.wind?.bearing?.value?.en,
|
|
205
|
+
pressure: (o.pressure?.value?.en || 0) * 10,
|
|
206
|
+
conditions: o.condition?.en || "Unknown",
|
|
207
|
+
confidence: 100,
|
|
208
|
+
raw: {
|
|
209
|
+
source: "environment-canada-citypage",
|
|
210
|
+
currentConditions: o
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
let s = i.forecastGroup;
|
|
214
|
+
if (s && s.forecasts) {
|
|
215
|
+
let e = /* @__PURE__ */ new Date();
|
|
216
|
+
for (let t of s.forecasts) {
|
|
217
|
+
let n = t.period, i = t.temperatures?.temperature || [], a = i.find((e) => e.class?.en === "high"), o = i.find((e) => e.class?.en === "low");
|
|
218
|
+
n?.textForecastName?.en?.toLowerCase() === "tonight" || n?.value?.en?.toLowerCase().includes("night") ? e.setHours(18, 0, 0, 0) : e.setHours(6, 0, 0, 0), r.push({
|
|
219
|
+
timestamp: new Date(e),
|
|
220
|
+
temperature: a?.value?.en || o?.value?.en || 0,
|
|
221
|
+
temperatureMax: a?.value?.en,
|
|
222
|
+
temperatureMin: o?.value?.en,
|
|
223
|
+
humidity: t.relativeHumidity?.value?.en || 0,
|
|
224
|
+
windSpeed: Number(t.winds?.periods?.[0]?.speed?.value?.en) || 0,
|
|
225
|
+
windDirection: t.winds?.periods?.[0]?.bearing?.value?.en,
|
|
226
|
+
conditions: t.cloudPrecip?.en || t.textSummary?.en || "Unknown",
|
|
227
|
+
precipAmount: t.precipitation?.accumulation?.amount?.value?.en,
|
|
228
|
+
confidence: 90,
|
|
229
|
+
raw: {
|
|
230
|
+
source: "environment-canada-citypage",
|
|
231
|
+
periodName: n?.textForecastName?.en || n?.value?.en,
|
|
232
|
+
period: t
|
|
233
|
+
}
|
|
234
|
+
}), e = n?.textForecastName?.en?.toLowerCase().includes("night") ? new Date(e.getTime() + 720 * 60 * 1e3) : new Date(e.getTime() + 1440 * 60 * 1e3);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return r;
|
|
238
|
+
}
|
|
239
|
+
transformClimateHourly(e, t, n, r) {
|
|
240
|
+
if (!e.features || e.features.length === 0) throw new a(this.name, t, n);
|
|
241
|
+
let i = /* @__PURE__ */ new Map();
|
|
242
|
+
for (let r of e.features) {
|
|
243
|
+
let e = r.properties, a = e.UTC_DATE ? E(e.UTC_DATE) : null;
|
|
244
|
+
if (!a || Number.isNaN(a.getTime())) continue;
|
|
245
|
+
let o = e.LATITUDE_DECIMAL_DEGREES || r.geometry?.coordinates[1], s = e.LONGITUDE_DECIMAL_DEGREES || r.geometry?.coordinates[0];
|
|
246
|
+
if (typeof o != "number" || typeof s != "number") continue;
|
|
247
|
+
let c = typeof e.TEMP == "number" ? e.TEMP : void 0;
|
|
248
|
+
if (c === void 0) continue;
|
|
249
|
+
let l = String(e.CLIMATE_IDENTIFIER || e.STN_ID || e.STATION_NAME || "nearby"), u = C(t, n, o, s), d = e.WEATHER_ENG_DESC || "Unknown", f = D(e.WIND_DIRECTION), p = {
|
|
250
|
+
timestamp: a,
|
|
251
|
+
temperature: x(c),
|
|
252
|
+
feelsLike: typeof e.WINDCHILL == "number" ? x(e.WINDCHILL) : typeof e.HUMIDEX == "number" ? x(e.HUMIDEX) : void 0,
|
|
253
|
+
humidity: x(e.RELATIVE_HUMIDITY || 0),
|
|
254
|
+
windSpeed: x(e.WIND_SPEED || 0),
|
|
255
|
+
windDirection: f,
|
|
256
|
+
pressure: typeof e.STATION_PRESSURE == "number" ? x(e.STATION_PRESSURE * 10) : void 0,
|
|
257
|
+
conditions: d.toUpperCase() === "NA" ? "Unknown" : d,
|
|
258
|
+
visibility: typeof e.VISIBILITY == "number" ? e.VISIBILITY : void 0,
|
|
259
|
+
precipAmount: typeof e.PRECIP_AMOUNT == "number" ? e.PRECIP_AMOUNT : void 0,
|
|
260
|
+
confidence: 100,
|
|
261
|
+
raw: {
|
|
262
|
+
source: "environment-canada-climate-hourly",
|
|
263
|
+
stationId: l,
|
|
264
|
+
stationName: e.STATION_NAME,
|
|
265
|
+
stationLatitude: o,
|
|
266
|
+
stationLongitude: s,
|
|
267
|
+
stationDistanceKm: u,
|
|
268
|
+
observation: e
|
|
269
|
+
}
|
|
270
|
+
}, m = i.get(l);
|
|
271
|
+
m ? m.forecasts.push(p) : i.set(l, {
|
|
272
|
+
distance: u,
|
|
273
|
+
forecasts: [p]
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
let o = [...i.values()].map((e) => ({
|
|
277
|
+
distance: e.distance,
|
|
278
|
+
forecasts: r ? l(e.forecasts, r) : e.forecasts.sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime())
|
|
279
|
+
})).filter((e) => e.forecasts.length > 0).sort((e, t) => e.distance - t.distance)[0];
|
|
280
|
+
if (!o) throw new a(this.name, t, n);
|
|
281
|
+
return o.forecasts.sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime());
|
|
282
|
+
}
|
|
283
|
+
async testConnection() {
|
|
284
|
+
try {
|
|
285
|
+
let e = this.buildApiUrl(51.0447, -114.0719), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
|
|
286
|
+
return clearTimeout(n), r.ok;
|
|
287
|
+
} catch {
|
|
288
|
+
return !1;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async supportsLocation(e, t) {
|
|
292
|
+
return { valid: !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180 }.valid ? S(e, t) : !1;
|
|
293
|
+
}
|
|
1254
294
|
};
|
|
1255
|
-
function
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
295
|
+
function E(e) {
|
|
296
|
+
let t = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(e);
|
|
297
|
+
return new Date(t ? e : `${e}Z`);
|
|
298
|
+
}
|
|
299
|
+
function D(e) {
|
|
300
|
+
if (typeof e == "number") return e <= 36 ? e * 10 : e;
|
|
301
|
+
}
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/providers/google-weather.ts
|
|
304
|
+
var O = class {
|
|
305
|
+
name = "Google Weather";
|
|
306
|
+
providerType = "commercial";
|
|
307
|
+
apiBase = "https://weather.googleapis.com/v1";
|
|
308
|
+
apiKey;
|
|
309
|
+
timeout;
|
|
310
|
+
constructor(e) {
|
|
311
|
+
if (!e.apiKey) throw new r("Google Weather", "API key is required");
|
|
312
|
+
this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
|
|
313
|
+
}
|
|
314
|
+
async fetchForLocation(e, n, r) {
|
|
315
|
+
m(this.name, e, n);
|
|
316
|
+
try {
|
|
317
|
+
let [t, i] = await Promise.all([this.fetchHourlyForecast(e, n, r), this.fetchDailyForecast(e, n, r)]), a = [...t, ...i];
|
|
318
|
+
return r?.limit && r.limit > 0 ? a.slice(0, r.limit) : a;
|
|
319
|
+
} catch (e) {
|
|
320
|
+
throw e instanceof t ? e : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async fetchCurrentConditions(e, t, n) {
|
|
324
|
+
m(this.name, e, t);
|
|
325
|
+
let r = this.buildUrl("/currentConditions:lookup", e, t), i = await this.fetchWithTimeout(r, n?.timeout);
|
|
326
|
+
return this.transformCurrentConditions(i);
|
|
327
|
+
}
|
|
328
|
+
async fetchHourlyForecast(e, t, n) {
|
|
329
|
+
m(this.name, e, t);
|
|
330
|
+
let r = n?.hours || 240, i = [], o;
|
|
331
|
+
do {
|
|
332
|
+
let a = this.buildUrl("/forecast/hours:lookup", e, t, `&hours=${r}`);
|
|
333
|
+
o && (a += `&pageToken=${encodeURIComponent(o)}`);
|
|
334
|
+
let s = await this.fetchWithTimeout(a, n?.timeout);
|
|
335
|
+
s.forecastHours && i.push(...s.forecastHours), o = s.nextPageToken;
|
|
336
|
+
} while (o && i.length < r);
|
|
337
|
+
if (i.length === 0) throw new a(this.name, e, t);
|
|
338
|
+
return i.slice(0, r).map((e) => this.transformHourlyForecast(e, "hourly"));
|
|
339
|
+
}
|
|
340
|
+
async fetchDailyForecast(e, t, n) {
|
|
341
|
+
m(this.name, e, t);
|
|
342
|
+
let r = n?.days || 10, i = [], o;
|
|
343
|
+
do {
|
|
344
|
+
let a = this.buildUrl("/forecast/days:lookup", e, t, `&days=${r}`);
|
|
345
|
+
o && (a += `&pageToken=${encodeURIComponent(o)}`);
|
|
346
|
+
let s = await this.fetchWithTimeout(a, n?.timeout);
|
|
347
|
+
s.forecastDays && i.push(...s.forecastDays), o = s.nextPageToken;
|
|
348
|
+
} while (o && i.length < r);
|
|
349
|
+
if (i.length === 0) throw new a(this.name, e, t);
|
|
350
|
+
return i.slice(0, r).map((e) => this.transformDailyForecast(e));
|
|
351
|
+
}
|
|
352
|
+
async fetchHourlyHistory(e, t, n) {
|
|
353
|
+
m(this.name, e, t);
|
|
354
|
+
let r = n?.hours || 24, i = this.buildUrl("/history/hours:lookup", e, t, `&hours=${r}`), o = await this.fetchWithTimeout(i, n?.timeout);
|
|
355
|
+
if (!o.historyHours || o.historyHours.length === 0) throw new a(this.name, e, t);
|
|
356
|
+
return o.historyHours.map((e) => this.transformHourlyForecast(e, "history"));
|
|
357
|
+
}
|
|
358
|
+
async fetchHistoricalForLocation(e, n, r) {
|
|
359
|
+
m(this.name, e, n);
|
|
360
|
+
let i = c(this.name, r), s = /* @__PURE__ */ new Date(), d = /* @__PURE__ */ new Date(s.getTime() - 1440 * 60 * 1e3);
|
|
361
|
+
if (i.end.getTime() > s.getTime()) throw new t("Google Weather historical lookup cannot end in the future", this.name, "INVALID_DATE_RANGE");
|
|
362
|
+
if (i.start.getTime() < d.getTime()) throw new o(this.name, "historical-weather", "Google Weather hourly history is limited to the previous 24 hours.");
|
|
363
|
+
let f = Math.min(24, u(i.start, s)), p = l(await this.fetchHourlyHistory(e, n, {
|
|
364
|
+
...r,
|
|
365
|
+
hours: f
|
|
366
|
+
}), i, r.limit);
|
|
367
|
+
if (p.length === 0) throw new a(this.name, e, n);
|
|
368
|
+
return p;
|
|
369
|
+
}
|
|
370
|
+
async fetchAlerts(e, t, n) {
|
|
371
|
+
m(this.name, e, t);
|
|
372
|
+
let r = this.buildUrl("/publicAlerts:lookup", e, t), i = await this.fetchWithTimeout(r, n?.timeout);
|
|
373
|
+
return !i.alerts || i.alerts.length === 0 ? [] : i.alerts.map((e) => this.transformAlert(e));
|
|
374
|
+
}
|
|
375
|
+
async testConnection() {
|
|
376
|
+
try {
|
|
377
|
+
let e = this.buildUrl("/currentConditions:lookup", 37.422, -122.084), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
|
|
378
|
+
return clearTimeout(n), r.ok;
|
|
379
|
+
} catch {
|
|
380
|
+
return !1;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async supportsLocation(e, t) {
|
|
384
|
+
return !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180;
|
|
385
|
+
}
|
|
386
|
+
buildUrl(e, t, n, r) {
|
|
387
|
+
let i = new URLSearchParams({
|
|
388
|
+
key: this.apiKey,
|
|
389
|
+
"location.latitude": t.toString(),
|
|
390
|
+
"location.longitude": n.toString()
|
|
391
|
+
}), a = `${this.apiBase}${e}?${i.toString()}`;
|
|
392
|
+
return r && (a += r), a;
|
|
393
|
+
}
|
|
394
|
+
async fetchWithTimeout(e, n) {
|
|
395
|
+
let r = new AbortController(), i = setTimeout(() => r.abort(), n || this.timeout);
|
|
396
|
+
try {
|
|
397
|
+
let t = await fetch(e, { signal: r.signal });
|
|
398
|
+
return clearTimeout(i), t.ok || await this.handleHttpError(t), await t.json();
|
|
399
|
+
} catch (e) {
|
|
400
|
+
throw clearTimeout(i), e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async handleHttpError(e) {
|
|
404
|
+
if (e.status === 401 || e.status === 403) throw new r(this.name, "Invalid API key");
|
|
405
|
+
if (e.status === 429) throw new n(this.name);
|
|
406
|
+
let i = await e.text();
|
|
407
|
+
throw new t(`API request failed: ${e.statusText} - ${i}`, this.name, `HTTP_${e.status}`);
|
|
408
|
+
}
|
|
409
|
+
transformCurrentConditions(e) {
|
|
410
|
+
return {
|
|
411
|
+
timestamp: new Date(e.currentTime),
|
|
412
|
+
temperature: x(e.temperature.degrees),
|
|
413
|
+
feelsLike: e.feelsLikeTemperature ? x(e.feelsLikeTemperature.degrees) : void 0,
|
|
414
|
+
conditions: e.weatherCondition.description.text,
|
|
415
|
+
humidity: x(e.relativeHumidity),
|
|
416
|
+
windSpeed: x(_(e.wind.speed.value)),
|
|
417
|
+
windDirection: e.wind.direction.degrees,
|
|
418
|
+
windGust: e.wind.gust ? x(_(e.wind.gust.value)) : void 0,
|
|
419
|
+
pressure: e.airPressure ? x(e.airPressure.meanSeaLevelMillibars) : void 0,
|
|
420
|
+
cloudCover: e.cloudCover,
|
|
421
|
+
visibility: e.visibility ? x(y(e.visibility.distance)) : void 0,
|
|
422
|
+
precipProbability: e.precipitation?.probability?.percent,
|
|
423
|
+
precipAmount: e.precipitation?.qpf?.quantity,
|
|
424
|
+
confidence: 95,
|
|
425
|
+
raw: {
|
|
426
|
+
source: "google-weather-current",
|
|
427
|
+
isDaytime: e.isDaytime,
|
|
428
|
+
uvIndex: e.uvIndex,
|
|
429
|
+
thunderstormProbability: e.thunderstormProbability,
|
|
430
|
+
timeZone: e.timeZone.id
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
transformHourlyForecast(e, t) {
|
|
435
|
+
return {
|
|
436
|
+
timestamp: new Date(e.interval.startTime),
|
|
437
|
+
temperature: x(e.temperature.degrees),
|
|
438
|
+
feelsLike: e.feelsLikeTemperature ? x(e.feelsLikeTemperature.degrees) : void 0,
|
|
439
|
+
conditions: e.weatherCondition.description.text,
|
|
440
|
+
humidity: x(e.relativeHumidity),
|
|
441
|
+
windSpeed: x(_(e.wind.speed.value)),
|
|
442
|
+
windDirection: e.wind.direction.degrees,
|
|
443
|
+
windGust: e.wind.gust ? x(_(e.wind.gust.value)) : void 0,
|
|
444
|
+
pressure: e.airPressure ? x(e.airPressure.meanSeaLevelMillibars) : void 0,
|
|
445
|
+
cloudCover: e.cloudCover,
|
|
446
|
+
visibility: e.visibility ? x(y(e.visibility.distance)) : void 0,
|
|
447
|
+
precipProbability: e.precipitation?.probability?.percent,
|
|
448
|
+
precipAmount: e.precipitation?.qpf?.quantity,
|
|
449
|
+
confidence: t === "history" ? 100 : 95,
|
|
450
|
+
raw: {
|
|
451
|
+
source: `google-weather-${t}`,
|
|
452
|
+
isDaytime: e.isDaytime,
|
|
453
|
+
uvIndex: e.uvIndex,
|
|
454
|
+
thunderstormProbability: e.thunderstormProbability,
|
|
455
|
+
displayDateTime: e.displayDateTime
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
transformDailyForecast(e) {
|
|
460
|
+
let n = e.daytimeForecast || e.nighttimeForecast;
|
|
461
|
+
if (!n) throw new t("Daily forecast missing both daytime and nighttime data", this.name, "INVALID_DATA");
|
|
462
|
+
return {
|
|
463
|
+
timestamp: new Date(e.interval.startTime),
|
|
464
|
+
temperature: e.maxTemperature ? x(e.maxTemperature.degrees) : x(n.temperature.degrees),
|
|
465
|
+
temperatureMax: e.maxTemperature ? x(e.maxTemperature.degrees) : void 0,
|
|
466
|
+
temperatureMin: e.minTemperature ? x(e.minTemperature.degrees) : void 0,
|
|
467
|
+
feelsLike: e.feelsLikeMaxTemperature ? x(e.feelsLikeMaxTemperature.degrees) : n.feelsLikeTemperature ? x(n.feelsLikeTemperature.degrees) : void 0,
|
|
468
|
+
conditions: n.weatherCondition.description.text,
|
|
469
|
+
humidity: x(n.relativeHumidity),
|
|
470
|
+
windSpeed: x(_(n.wind.speed.value)),
|
|
471
|
+
windDirection: n.wind.direction.degrees,
|
|
472
|
+
windGust: n.wind.gust ? x(_(n.wind.gust.value)) : void 0,
|
|
473
|
+
pressure: n.airPressure ? x(n.airPressure.meanSeaLevelMillibars) : void 0,
|
|
474
|
+
cloudCover: n.cloudCover,
|
|
475
|
+
visibility: n.visibility ? x(y(n.visibility.distance)) : void 0,
|
|
476
|
+
precipProbability: n.precipitation?.probability?.percent,
|
|
477
|
+
precipAmount: n.precipitation?.qpf?.quantity,
|
|
478
|
+
confidence: 90,
|
|
479
|
+
raw: {
|
|
480
|
+
source: "google-weather-daily",
|
|
481
|
+
displayDate: e.displayDate,
|
|
482
|
+
daytimeForecast: e.daytimeForecast,
|
|
483
|
+
nighttimeForecast: e.nighttimeForecast
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
transformAlert(e) {
|
|
488
|
+
return {
|
|
489
|
+
id: e.alertId,
|
|
490
|
+
headline: e.headline || "Weather Alert",
|
|
491
|
+
description: e.description || "",
|
|
492
|
+
severity: e.severity || "unknown",
|
|
493
|
+
startTime: e.effective ? new Date(e.effective) : /* @__PURE__ */ new Date(),
|
|
494
|
+
endTime: e.expires ? new Date(e.expires) : /* @__PURE__ */ new Date(),
|
|
495
|
+
raw: e
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}, k = 7200 * 60 * 1e3, A = class {
|
|
499
|
+
name = "Open-Meteo";
|
|
500
|
+
providerType = "community";
|
|
501
|
+
forecastApiBase = "https://api.open-meteo.com/v1/forecast";
|
|
502
|
+
archiveApiBase = "https://archive-api.open-meteo.com/v1/archive";
|
|
503
|
+
timeout;
|
|
504
|
+
constructor(e = {}) {
|
|
505
|
+
this.timeout = e.timeout || 1e4;
|
|
506
|
+
}
|
|
507
|
+
async fetchForLocation(e, t, n) {
|
|
508
|
+
m(this.name, e, t);
|
|
509
|
+
let r = this.buildBaseParams(e, t);
|
|
510
|
+
r.set("forecast_days", "7");
|
|
511
|
+
let i = await this.fetchJson(`${this.forecastApiBase}?${r.toString()}`, n?.timeout), o = this.transformHourlyData(i, "open-meteo-forecast");
|
|
512
|
+
if (o.length === 0) throw new a(this.name, e, t);
|
|
513
|
+
return n?.limit && n.limit > 0 ? o.slice(0, n.limit) : o;
|
|
514
|
+
}
|
|
515
|
+
async fetchHistoricalForLocation(e, n, r) {
|
|
516
|
+
m(this.name, e, n);
|
|
517
|
+
let i = c(this.name, r), o = /* @__PURE__ */ new Date(Date.now() - k);
|
|
518
|
+
if (i.end.getTime() > o.getTime()) throw new t("Open-Meteo archive data is published with a 5-7 day lag", this.name, "INVALID_DATE_RANGE", {
|
|
519
|
+
requestedEnd: i.end.toISOString(),
|
|
520
|
+
latestSupportedEnd: o.toISOString()
|
|
521
|
+
});
|
|
522
|
+
let s = this.buildBaseParams(e, n);
|
|
523
|
+
s.set("start_date", d(i.start)), s.set("end_date", d(i.end));
|
|
524
|
+
let u = await this.fetchJson(`${this.archiveApiBase}?${s.toString()}`, r.timeout), f = l(this.transformHourlyData(u, "open-meteo-archive"), i, r.limit);
|
|
525
|
+
if (f.length === 0) throw new a(this.name, e, n);
|
|
526
|
+
return f;
|
|
527
|
+
}
|
|
528
|
+
async testConnection() {
|
|
529
|
+
try {
|
|
530
|
+
return (await fetch(`${this.forecastApiBase}?latitude=51.0447&longitude=-114.0719&hourly=temperature_2m&forecast_days=1&timezone=UTC`)).ok;
|
|
531
|
+
} catch {
|
|
532
|
+
return !1;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async supportsLocation(e, t) {
|
|
536
|
+
return !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180;
|
|
537
|
+
}
|
|
538
|
+
buildBaseParams(e, t) {
|
|
539
|
+
return new URLSearchParams({
|
|
540
|
+
latitude: e.toString(),
|
|
541
|
+
longitude: t.toString(),
|
|
542
|
+
hourly: [
|
|
543
|
+
"temperature_2m",
|
|
544
|
+
"relative_humidity_2m",
|
|
545
|
+
"precipitation",
|
|
546
|
+
"weather_code",
|
|
547
|
+
"cloud_cover",
|
|
548
|
+
"wind_speed_10m",
|
|
549
|
+
"wind_direction_10m",
|
|
550
|
+
"wind_gusts_10m",
|
|
551
|
+
"visibility"
|
|
552
|
+
].join(","),
|
|
553
|
+
temperature_unit: "celsius",
|
|
554
|
+
wind_speed_unit: "kmh",
|
|
555
|
+
precipitation_unit: "mm",
|
|
556
|
+
timezone: "UTC"
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
async fetchJson(e, r) {
|
|
560
|
+
let i = new AbortController(), a = setTimeout(() => i.abort(), r || this.timeout);
|
|
561
|
+
try {
|
|
562
|
+
let r = await fetch(e, { signal: i.signal });
|
|
563
|
+
if (r.status === 429) throw new n(this.name);
|
|
564
|
+
if (!r.ok) throw new t(`API request failed: ${r.statusText}`, this.name, `HTTP_${r.status}`);
|
|
565
|
+
let a = await r.json();
|
|
566
|
+
if (a.error) throw new t(a.reason || "Open-Meteo API returned an error", this.name, "API_ERROR", a);
|
|
567
|
+
return a;
|
|
568
|
+
} catch (e) {
|
|
569
|
+
throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
|
|
570
|
+
} finally {
|
|
571
|
+
clearTimeout(a);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
transformHourlyData(e, t) {
|
|
575
|
+
let n = e.hourly;
|
|
576
|
+
if (!n?.time?.length) return [];
|
|
577
|
+
let r = [];
|
|
578
|
+
for (let i = 0; i < n.time.length; i++) {
|
|
579
|
+
let a = M(n.time[i]), o = n.temperature_2m?.[i], s = j(n.relative_humidity_2m?.[i]), c = j(n.wind_speed_10m?.[i]);
|
|
580
|
+
if (Number.isNaN(a.getTime()) || typeof o != "number" || s === void 0 || c === void 0) continue;
|
|
581
|
+
let l = n.weather_code?.[i];
|
|
582
|
+
r.push({
|
|
583
|
+
timestamp: a,
|
|
584
|
+
temperature: x(o),
|
|
585
|
+
humidity: x(s),
|
|
586
|
+
windSpeed: x(c),
|
|
587
|
+
windDirection: j(n.wind_direction_10m?.[i]),
|
|
588
|
+
windGust: typeof n.wind_gusts_10m?.[i] == "number" ? x(n.wind_gusts_10m[i]) : void 0,
|
|
589
|
+
conditions: N(l),
|
|
590
|
+
cloudCover: j(n.cloud_cover?.[i]),
|
|
591
|
+
visibility: typeof n.visibility?.[i] == "number" ? x(y(n.visibility[i])) : void 0,
|
|
592
|
+
precipAmount: j(n.precipitation?.[i]),
|
|
593
|
+
confidence: t === "open-meteo-archive" ? 100 : 90,
|
|
594
|
+
raw: {
|
|
595
|
+
source: t,
|
|
596
|
+
weatherCode: l,
|
|
597
|
+
latitude: e.latitude,
|
|
598
|
+
longitude: e.longitude,
|
|
599
|
+
timezone: e.timezone,
|
|
600
|
+
units: e.hourly_units
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return r.sort((e, t) => e.timestamp.getTime() - t.timestamp.getTime());
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
function j(e) {
|
|
608
|
+
return typeof e == "number" ? e : void 0;
|
|
609
|
+
}
|
|
610
|
+
function M(e) {
|
|
611
|
+
let t = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(e);
|
|
612
|
+
return new Date(t ? e : `${e}Z`);
|
|
613
|
+
}
|
|
614
|
+
function N(e) {
|
|
615
|
+
switch (e) {
|
|
616
|
+
case 0: return "Clear sky";
|
|
617
|
+
case 1: return "Mainly clear";
|
|
618
|
+
case 2: return "Partly cloudy";
|
|
619
|
+
case 3: return "Overcast";
|
|
620
|
+
case 45:
|
|
621
|
+
case 48: return "Fog";
|
|
622
|
+
case 51:
|
|
623
|
+
case 53:
|
|
624
|
+
case 55: return "Drizzle";
|
|
625
|
+
case 56:
|
|
626
|
+
case 57: return "Freezing drizzle";
|
|
627
|
+
case 61:
|
|
628
|
+
case 63:
|
|
629
|
+
case 65: return "Rain";
|
|
630
|
+
case 66:
|
|
631
|
+
case 67: return "Freezing rain";
|
|
632
|
+
case 71:
|
|
633
|
+
case 73:
|
|
634
|
+
case 75: return "Snow";
|
|
635
|
+
case 77: return "Snow grains";
|
|
636
|
+
case 80:
|
|
637
|
+
case 81:
|
|
638
|
+
case 82: return "Rain showers";
|
|
639
|
+
case 85:
|
|
640
|
+
case 86: return "Snow showers";
|
|
641
|
+
case 95: return "Thunderstorm";
|
|
642
|
+
case 96:
|
|
643
|
+
case 99: return "Thunderstorm with hail";
|
|
644
|
+
default: return "Unknown";
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
//#endregion
|
|
648
|
+
//#region src/providers/openweathermap.ts
|
|
649
|
+
var P = class {
|
|
650
|
+
name = "OpenWeatherMap";
|
|
651
|
+
providerType = "commercial";
|
|
652
|
+
apiBase = "https://api.openweathermap.org/data/2.5/forecast";
|
|
653
|
+
apiKey;
|
|
654
|
+
timeout;
|
|
655
|
+
constructor(e) {
|
|
656
|
+
if (!e.apiKey) throw new r("OpenWeatherMap", "API key is required");
|
|
657
|
+
this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
|
|
658
|
+
}
|
|
659
|
+
async fetchForLocation(e, i, a) {
|
|
660
|
+
m(this.name, e, i);
|
|
661
|
+
try {
|
|
662
|
+
let o = this.buildApiUrl(e, i), s = new AbortController(), c = setTimeout(() => s.abort(), a?.timeout || this.timeout), l = await fetch(o, { signal: s.signal });
|
|
663
|
+
if (clearTimeout(c), !l.ok) throw l.status === 401 || l.status === 403 ? new r(this.name, "Invalid API key") : l.status === 429 ? new n(this.name) : new t(`API request failed: ${l.statusText}`, this.name, `HTTP_${l.status}`);
|
|
664
|
+
let u = await l.json(), d = this.transformForecasts(u);
|
|
665
|
+
return a?.limit && a.limit > 0 ? d.slice(0, a.limit) : d;
|
|
666
|
+
} catch (e) {
|
|
667
|
+
throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async fetchHistoricalForLocation(e, t, n) {
|
|
671
|
+
throw m(this.name, e, t), new o(this.name, "historical-weather", "The OpenWeatherMap forecast adapter does not expose OpenWeatherMap historical products yet.");
|
|
672
|
+
}
|
|
673
|
+
buildApiUrl(e, t) {
|
|
674
|
+
let n = new URLSearchParams({
|
|
675
|
+
lat: e.toString(),
|
|
676
|
+
lon: t.toString(),
|
|
677
|
+
appid: this.apiKey,
|
|
678
|
+
units: "metric",
|
|
679
|
+
cnt: "40"
|
|
680
|
+
});
|
|
681
|
+
return `${this.apiBase}?${n.toString()}`;
|
|
682
|
+
}
|
|
683
|
+
transformForecasts(e) {
|
|
684
|
+
let t = [];
|
|
685
|
+
for (let n of e.list) {
|
|
686
|
+
let r = /* @__PURE__ */ new Date(n.dt * 1e3);
|
|
687
|
+
t.push({
|
|
688
|
+
timestamp: r,
|
|
689
|
+
temperature: x(n.main.temp),
|
|
690
|
+
feelsLike: x(n.main.feels_like),
|
|
691
|
+
temperatureMin: x(n.main.temp_min),
|
|
692
|
+
temperatureMax: x(n.main.temp_max),
|
|
693
|
+
pressure: x(n.main.pressure),
|
|
694
|
+
humidity: x(n.main.humidity),
|
|
695
|
+
windSpeed: x(_(n.wind.speed)),
|
|
696
|
+
windDirection: n.wind.deg,
|
|
697
|
+
windGust: n.wind.gust ? x(_(n.wind.gust)) : void 0,
|
|
698
|
+
conditions: n.weather[0]?.description || "Unknown",
|
|
699
|
+
cloudCover: n.clouds.all,
|
|
700
|
+
visibility: n.visibility ? x(y(n.visibility)) : void 0,
|
|
701
|
+
precipProbability: x(n.pop * 100),
|
|
702
|
+
precipAmount: n.rain?.["3h"] || n.snow?.["3h"] || 0,
|
|
703
|
+
confidence: 95,
|
|
704
|
+
raw: {
|
|
705
|
+
source: "openweathermap-5day-3hour",
|
|
706
|
+
timestamp: n.dt,
|
|
707
|
+
weather: n.weather,
|
|
708
|
+
partOfDay: n.sys.pod,
|
|
709
|
+
cityData: {
|
|
710
|
+
name: e.city.name,
|
|
711
|
+
country: e.city.country,
|
|
712
|
+
timezone: e.city.timezone
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
return t;
|
|
718
|
+
}
|
|
719
|
+
async testConnection() {
|
|
720
|
+
try {
|
|
721
|
+
let e = this.buildApiUrl(51.0447, -114.0719), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
|
|
722
|
+
return clearTimeout(n), r.ok;
|
|
723
|
+
} catch {
|
|
724
|
+
return !1;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
async supportsLocation(e, t) {
|
|
728
|
+
return { valid: !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180 }.valid;
|
|
729
|
+
}
|
|
730
|
+
}, F = class {
|
|
731
|
+
name = "OpenWeatherMap One Call";
|
|
732
|
+
providerType = "commercial";
|
|
733
|
+
apiBase = "https://api.openweathermap.org/data/3.0/onecall";
|
|
734
|
+
apiKey;
|
|
735
|
+
timeout;
|
|
736
|
+
constructor(e) {
|
|
737
|
+
if (!e.apiKey) throw new r("OpenWeatherMap One Call", "API key is required");
|
|
738
|
+
this.apiKey = e.apiKey, this.timeout = e.timeout || 1e4;
|
|
739
|
+
}
|
|
740
|
+
async fetchForLocation(e, i, a) {
|
|
741
|
+
m(this.name, e, i);
|
|
742
|
+
try {
|
|
743
|
+
let o = this.buildApiUrl(e, i), s = new AbortController(), c = setTimeout(() => s.abort(), a?.timeout || this.timeout), l = await fetch(o, { signal: s.signal });
|
|
744
|
+
if (clearTimeout(c), !l.ok) {
|
|
745
|
+
if (l.status === 401 || l.status === 403) throw new r(this.name, "Invalid API key");
|
|
746
|
+
if (l.status === 429) throw new n(this.name);
|
|
747
|
+
let e = await l.text();
|
|
748
|
+
throw new t(`API request failed: ${l.statusText} - ${e}`, this.name, `HTTP_${l.status}`);
|
|
749
|
+
}
|
|
750
|
+
let u = await l.json(), d = this.transformForecasts(u);
|
|
751
|
+
return a?.limit && a.limit > 0 ? d.slice(0, a.limit) : d;
|
|
752
|
+
} catch (e) {
|
|
753
|
+
throw e instanceof t ? e : e.name === "AbortError" ? new t("Request timeout", this.name, "TIMEOUT") : new t(`Failed to fetch weather data: ${e.message}`, this.name, "FETCH_ERROR");
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async fetchHistoricalForLocation(e, t, n) {
|
|
757
|
+
throw m(this.name, e, t), new o(this.name, "historical-weather", "OpenWeatherMap One Call historical data is not exposed through the standardized adapter yet.");
|
|
758
|
+
}
|
|
759
|
+
buildApiUrl(e, t) {
|
|
760
|
+
let n = new URLSearchParams({
|
|
761
|
+
lat: e.toString(),
|
|
762
|
+
lon: t.toString(),
|
|
763
|
+
appid: this.apiKey,
|
|
764
|
+
units: "metric",
|
|
765
|
+
exclude: "minutely,alerts"
|
|
766
|
+
});
|
|
767
|
+
return `${this.apiBase}?${n.toString()}`;
|
|
768
|
+
}
|
|
769
|
+
transformForecasts(e) {
|
|
770
|
+
let t = [];
|
|
771
|
+
if (e.hourly) for (let n of e.hourly) {
|
|
772
|
+
let e = /* @__PURE__ */ new Date(n.dt * 1e3);
|
|
773
|
+
t.push({
|
|
774
|
+
timestamp: e,
|
|
775
|
+
temperature: x(n.temp),
|
|
776
|
+
feelsLike: x(n.feels_like),
|
|
777
|
+
pressure: x(n.pressure),
|
|
778
|
+
humidity: x(n.humidity),
|
|
779
|
+
windSpeed: x(_(n.wind_speed)),
|
|
780
|
+
windDirection: n.wind_deg,
|
|
781
|
+
windGust: n.wind_gust ? x(_(n.wind_gust)) : void 0,
|
|
782
|
+
conditions: n.weather[0]?.description || "Unknown",
|
|
783
|
+
cloudCover: n.clouds,
|
|
784
|
+
visibility: n.visibility ? x(y(n.visibility)) : void 0,
|
|
785
|
+
precipProbability: x(n.pop * 100),
|
|
786
|
+
precipAmount: n.rain?.["1h"] || n.snow?.["1h"] || 0,
|
|
787
|
+
confidence: 95,
|
|
788
|
+
raw: {
|
|
789
|
+
source: "openweathermap-onecall-hourly",
|
|
790
|
+
timestamp: n.dt,
|
|
791
|
+
weather: n.weather
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
if (e.daily) for (let n of e.daily) {
|
|
796
|
+
let e = /* @__PURE__ */ new Date(n.dt * 1e3);
|
|
797
|
+
t.push({
|
|
798
|
+
timestamp: e,
|
|
799
|
+
temperature: x(n.temp.day),
|
|
800
|
+
temperatureMax: x(n.temp.max),
|
|
801
|
+
temperatureMin: x(n.temp.min),
|
|
802
|
+
feelsLike: x(n.feels_like.day),
|
|
803
|
+
pressure: x(n.pressure),
|
|
804
|
+
humidity: x(n.humidity),
|
|
805
|
+
windSpeed: x(_(n.wind_speed)),
|
|
806
|
+
windDirection: n.wind_deg,
|
|
807
|
+
windGust: n.wind_gust ? x(_(n.wind_gust)) : void 0,
|
|
808
|
+
conditions: n.weather[0]?.description || "Unknown",
|
|
809
|
+
cloudCover: n.clouds,
|
|
810
|
+
precipProbability: x(n.pop * 100),
|
|
811
|
+
precipAmount: n.rain || n.snow || 0,
|
|
812
|
+
confidence: 95,
|
|
813
|
+
raw: {
|
|
814
|
+
source: "openweathermap-onecall-daily",
|
|
815
|
+
timestamp: n.dt,
|
|
816
|
+
weather: n.weather,
|
|
817
|
+
sunrise: n.sunrise,
|
|
818
|
+
sunset: n.sunset,
|
|
819
|
+
uvi: n.uvi
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return t;
|
|
824
|
+
}
|
|
825
|
+
async testConnection() {
|
|
826
|
+
try {
|
|
827
|
+
let e = this.buildApiUrl(51.0447, -114.0719), t = new AbortController(), n = setTimeout(() => t.abort(), this.timeout), r = await fetch(e, { signal: t.signal });
|
|
828
|
+
return clearTimeout(n), r.ok;
|
|
829
|
+
} catch {
|
|
830
|
+
return !1;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async supportsLocation(e, t) {
|
|
834
|
+
return { valid: !Number.isNaN(e) && !Number.isNaN(t) && e >= -90 && e <= 90 && t >= -180 && t <= 180 }.valid;
|
|
835
|
+
}
|
|
836
|
+
}, I = {
|
|
837
|
+
provider: "HAVE_WEATHER_PROVIDER",
|
|
838
|
+
apiKey: "OPENWEATHER_API_KEY",
|
|
839
|
+
googleApiKey: "GOOGLE_API_KEY",
|
|
840
|
+
timeout: "HAVE_WEATHER_TIMEOUT"
|
|
1377
841
|
};
|
|
842
|
+
function L() {
|
|
843
|
+
let t = e(I), n = {};
|
|
844
|
+
if (t.provider && (n.provider = t.provider), t.apiKey && (n.apiKey = t.apiKey), t.googleApiKey && (n.googleApiKey = t.googleApiKey), t.timeout) {
|
|
845
|
+
let e = Number.parseInt(t.timeout, 10);
|
|
846
|
+
Number.isNaN(e) || (n.timeout = e);
|
|
847
|
+
}
|
|
848
|
+
return n;
|
|
849
|
+
}
|
|
850
|
+
function R(e) {
|
|
851
|
+
let n = L(), r = {
|
|
852
|
+
...n,
|
|
853
|
+
...e
|
|
854
|
+
}, i = r.provider || n.provider || "environment-canada";
|
|
855
|
+
switch (i) {
|
|
856
|
+
case "environment-canada": return {
|
|
857
|
+
provider: "environment-canada",
|
|
858
|
+
timeout: r.timeout
|
|
859
|
+
};
|
|
860
|
+
case "openweathermap":
|
|
861
|
+
if (!r.apiKey) throw new t("OpenWeatherMap requires an API key. Set OPENWEATHER_API_KEY environment variable or pass apiKey option.", "openweathermap", "MISSING_API_KEY");
|
|
862
|
+
return {
|
|
863
|
+
provider: "openweathermap",
|
|
864
|
+
apiKey: r.apiKey,
|
|
865
|
+
timeout: r.timeout
|
|
866
|
+
};
|
|
867
|
+
case "openweathermap-onecall":
|
|
868
|
+
if (!r.apiKey) throw new t("OpenWeatherMap One Call requires an API key. Set OPENWEATHER_API_KEY environment variable or pass apiKey option.", "openweathermap-onecall", "MISSING_API_KEY");
|
|
869
|
+
return {
|
|
870
|
+
provider: "openweathermap-onecall",
|
|
871
|
+
apiKey: r.apiKey,
|
|
872
|
+
timeout: r.timeout
|
|
873
|
+
};
|
|
874
|
+
case "google-weather": {
|
|
875
|
+
let e = r.apiKey === void 0 ? n.googleApiKey : r.apiKey;
|
|
876
|
+
if (!e) throw new t("Google Weather requires an API key. Set GOOGLE_API_KEY environment variable or pass apiKey option.", "google-weather", "MISSING_API_KEY");
|
|
877
|
+
return {
|
|
878
|
+
provider: "google-weather",
|
|
879
|
+
apiKey: e,
|
|
880
|
+
timeout: r.timeout
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
case "open-meteo": return {
|
|
884
|
+
provider: "open-meteo",
|
|
885
|
+
timeout: r.timeout
|
|
886
|
+
};
|
|
887
|
+
default: throw new t(`Unknown provider: ${i}. Supported providers: environment-canada, openweathermap, openweathermap-onecall, google-weather, open-meteo`, "unknown", "INVALID_PROVIDER");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async function z(e) {
|
|
891
|
+
let n = R(e);
|
|
892
|
+
switch (n.provider) {
|
|
893
|
+
case "environment-canada": return new T({ timeout: n.timeout });
|
|
894
|
+
case "openweathermap": return new P({
|
|
895
|
+
apiKey: n.apiKey,
|
|
896
|
+
timeout: n.timeout
|
|
897
|
+
});
|
|
898
|
+
case "openweathermap-onecall": return new F({
|
|
899
|
+
apiKey: n.apiKey,
|
|
900
|
+
timeout: n.timeout
|
|
901
|
+
});
|
|
902
|
+
case "google-weather": return new O({
|
|
903
|
+
apiKey: n.apiKey,
|
|
904
|
+
timeout: n.timeout
|
|
905
|
+
});
|
|
906
|
+
case "open-meteo": return new A({ timeout: n.timeout });
|
|
907
|
+
default: throw new t(`Unsupported provider: ${n.provider}`, "unknown", "INVALID_PROVIDER");
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
var B = !0;
|
|
911
|
+
//#endregion
|
|
912
|
+
export { r as AuthenticationError, i as InvalidLocationError, a as NoResultsError, B as PACKAGE_VERSION_INITIALIZED, n as RateLimitError, o as UnsupportedWeatherCapabilityError, t as WeatherError, C as calculateDistance, z as default, z as getWeatherAdapter, m as ensureValidCoordinates, g as fahrenheitToCelsius, S as isInCanada, h as kelvinToCelsius, _ as metersPerSecondToKmPerHour, y as metersToKilometers, v as milesPerHourToKmPerHour, b as milesToKilometers, p as validateCoordinates };
|