| 1 |
1 |
|
package main |
| 2 |
2 |
|
|
| 3 |
3 |
|
import ( |
|
4 |
+ |
"encoding/json" |
| 4 |
5 |
|
"fmt" |
|
6 |
+ |
"html/template" |
| 5 |
7 |
|
"net/http" |
|
8 |
+ |
"strconv" |
| 6 |
9 |
|
"strings" |
| 7 |
10 |
|
"time" |
| 8 |
|
- |
"encoding/json" |
| 9 |
|
- |
"strconv" |
| 10 |
11 |
|
) |
| 11 |
12 |
|
|
| 12 |
13 |
|
type parsedAttributes struct { |
|
| 35 |
36 |
|
} |
| 36 |
37 |
|
|
| 37 |
38 |
|
type Period struct { |
| 38 |
|
- |
Temperature int `json:"temperature"` |
| 39 |
|
- |
ShortForecast string `json:"shortForecast"` |
|
39 |
+ |
Temperature int `json:"temperature"` |
|
40 |
+ |
ShortForecast string `json:"shortForecast"` |
| 40 |
41 |
|
} |
| 41 |
42 |
|
|
| 42 |
43 |
|
type WeatherForecastResponse struct { |
| 43 |
|
- |
Properties struct { |
| 44 |
|
- |
Periods []Period `json:"periods"` |
| 45 |
|
- |
} `json:"properties"` |
|
44 |
+ |
Properties struct { |
|
45 |
+ |
Periods []Period `json:"periods"` |
|
46 |
+ |
} `json:"properties"` |
|
47 |
+ |
} |
|
48 |
+ |
|
|
49 |
+ |
type Weather struct { |
|
50 |
+ |
Icon template.HTML |
|
51 |
+ |
Conditions string |
|
52 |
+ |
Temperature string |
|
53 |
+ |
Location string |
| 46 |
54 |
|
} |
| 47 |
55 |
|
|
| 48 |
56 |
|
func parseAttributes(text string) parsedAttributes { |
|
| 349 |
357 |
|
} |
| 350 |
358 |
|
|
| 351 |
359 |
|
func getWeather(location string) string { |
|
360 |
+ |
if location == "" { |
|
361 |
+ |
return "" |
|
362 |
+ |
} |
| 352 |
363 |
|
// Fetch Points data using lat,long |
| 353 |
364 |
|
pointURL := fmt.Sprintf("https://api.weather.gov/points/%s", location) |
| 354 |
365 |
|
resp, err := http.Get(pointURL) |
|
| 375 |
386 |
|
temp := strconv.Itoa(weatherForecast.Properties.Periods[0].Temperature) |
| 376 |
387 |
|
conditions := weatherForecast.Properties.Periods[0].ShortForecast |
| 377 |
388 |
|
|
| 378 |
|
- |
weather := fmt.Sprintf("%s,%s,%s,%s", conditions, temp, city,state) |
|
389 |
+ |
weather := fmt.Sprintf("%s,%s,%s,%s", conditions, temp, city, state) |
| 379 |
390 |
|
return weather |
| 380 |
391 |
|
} |
| 381 |
392 |
|
|
|
393 |
+ |
func formatWeather(weather string) (Weather, error) { |
|
394 |
+ |
parts := strings.Split(weather, ",") |
|
395 |
+ |
if len(parts) < 4 { |
|
396 |
+ |
return Weather{}, fmt.Errorf("unexpected weather format: %q", weather) |
|
397 |
+ |
} |
|
398 |
+ |
for i := range parts { |
|
399 |
+ |
parts[i] = strings.TrimSpace(parts[i]) // handles ", " vs "," |
|
400 |
+ |
} |
|
401 |
+ |
|
|
402 |
+ |
conditions := parts[0] |
|
403 |
+ |
return Weather{ |
|
404 |
+ |
Conditions: conditions, |
|
405 |
+ |
Temperature: fmt.Sprintf("%s°F", parts[1]), |
|
406 |
+ |
Location: fmt.Sprintf("%s, %s", parts[2], parts[3]), |
|
407 |
+ |
Icon: weatherIcons[categorize(conditions)], |
|
408 |
+ |
}, nil |
|
409 |
+ |
} |
|
410 |
+ |
|
|
411 |
+ |
type WeatherCategory int |
|
412 |
+ |
|
|
413 |
+ |
const ( |
|
414 |
+ |
CatStorm WeatherCategory = iota |
|
415 |
+ |
CatSleet |
|
416 |
+ |
CatSnow |
|
417 |
+ |
CatRain |
|
418 |
+ |
CatFog |
|
419 |
+ |
CatPartlyCloudy |
|
420 |
+ |
CatCloudy |
|
421 |
+ |
CatClear |
|
422 |
+ |
CatUnknown |
|
423 |
+ |
) |
|
424 |
+ |
|
|
425 |
+ |
var categoryRules = []struct { |
|
426 |
+ |
category WeatherCategory |
|
427 |
+ |
keywords []string |
|
428 |
+ |
}{ |
|
429 |
+ |
{CatStorm, []string{"thunder", "tstorm", "storm"}}, |
|
430 |
+ |
{CatSleet, []string{"sleet", "freez", "frzg", "mix"}}, |
|
431 |
+ |
{CatSnow, []string{"snow", "flurr"}}, |
|
432 |
+ |
{CatRain, []string{"rain", "shower", "drizzle"}}, |
|
433 |
+ |
{CatFog, []string{"fog", "mist", "haze"}}, |
|
434 |
+ |
{CatPartlyCloudy, []string{"partly", "variable"}}, |
|
435 |
+ |
{CatCloudy, []string{"overcast", "cloud"}}, |
|
436 |
+ |
{CatClear, []string{"clear", "sunny", "sun", "fair"}}, |
|
437 |
+ |
} |
|
438 |
+ |
|
|
439 |
+ |
func categorize(conditions string) WeatherCategory { |
|
440 |
+ |
c := strings.ToLower(conditions) |
|
441 |
+ |
for _, rule := range categoryRules { |
|
442 |
+ |
for _, kw := range rule.keywords { |
|
443 |
+ |
if strings.Contains(c, kw) { |
|
444 |
+ |
return rule.category |
|
445 |
+ |
} |
|
446 |
+ |
} |
|
447 |
+ |
} |
|
448 |
+ |
return CatUnknown |
|
449 |
+ |
} |
|
450 |
+ |
|
|
451 |
+ |
var weatherIcons = map[WeatherCategory]template.HTML{ |
|
452 |
+ |
CatPartlyCloudy: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M164 76a71.9 71.9 0 0 0-22.14 3.48A51.8 51.8 0 0 0 129 63.83l11.56-16.51a4 4 0 0 0-6.56-4.59l-11.55 16.51A52 52 0 0 0 96 52c-1.71 0-3.4.09-5.06.25l-3.5-19.85a4 4 0 0 0-7.88 1.39l3.5 19.84A52.2 52.2 0 0 0 55.85 71L39.32 59.42A4 4 0 0 0 34.73 66l16.53 11.54A51.63 51.63 0 0 0 44 104c0 1.69.09 3.37.25 5l-19.85 3.5a4 4 0 0 0 .69 7.94a4 4 0 0 0 .7-.06l19.85-3.5A52.1 52.1 0 0 0 54 134.6A48 48 0 0 0 84 220h80a72 72 0 0 0 0-144M52 104a44 44 0 0 1 82.33-21.61a72.23 72.23 0 0 0-38.82 43A48.3 48.3 0 0 0 84 124a47.76 47.76 0 0 0-23.4 6.11A44 44 0 0 1 52 104m112 108H84a40 40 0 1 1 9.43-78.88A71.6 71.6 0 0 0 92 143.77a4 4 0 0 0 8 .46a64.3 64.3 0 0 1 2-12.67c0-.12.07-.24.09-.36A64.06 64.06 0 1 1 164 212"/></svg>`, |
|
453 |
+ |
CatClear: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M124 40v-8a4 4 0 0 1 8 0v8a4 4 0 0 1-8 0m64 88a60 60 0 1 1-60-60a60.07 60.07 0 0 1 60 60m-8 0a52 52 0 1 0-52 52a52.06 52.06 0 0 0 52-52M61.17 66.83a4 4 0 0 0 5.66-5.66l-8-8a4 4 0 0 0-5.66 5.66Zm0 122.34l-8 8a4 4 0 0 0 5.66 5.66l8-8a4 4 0 0 0-5.66-5.66m136-136l-8 8a4 4 0 0 0 5.66 5.66l8-8a4 4 0 1 0-5.66-5.66m-2.34 136a4 4 0 0 0-5.66 5.66l8 8a4 4 0 0 0 5.66-5.66ZM40 124h-8a4 4 0 0 0 0 8h8a4 4 0 0 0 0-8m88 88a4 4 0 0 0-4 4v8a4 4 0 0 0 8 0v-8a4 4 0 0 0-4-4m96-88h-8a4 4 0 0 0 0 8h8a4 4 0 0 0 0-8"/></svg>`, |
|
454 |
+ |
CatCloudy: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M160 44a84.11 84.11 0 0 0-76.41 49.12A60.7 60.7 0 0 0 72 92a60 60 0 0 0 0 120h88a84 84 0 0 0 0-168m0 160H72a52 52 0 1 1 8.55-103.3A83.7 83.7 0 0 0 76 128a4 4 0 0 0 8 0a76 76 0 1 1 76 76"/></svg>`, |
|
455 |
+ |
CatStorm: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M156 20a72.19 72.19 0 0 0-68.49 49.39A48 48 0 1 0 76 164h44.94l-20.37 33.94A4 4 0 0 0 104 204h32.94l-20.37 33.94a4 4 0 0 0 6.86 4.12l24-40A4 4 0 0 0 144 196h-32.94l19.2-32H156a72 72 0 0 0 0-144m0 136H76a40 40 0 1 1 9.43-78.88A71.6 71.6 0 0 0 84 87.77a4 4 0 0 0 8 .46A64.06 64.06 0 1 1 156 156"/></svg>`, |
|
456 |
+ |
CatRain: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="m155.33 194.22l-32 48a4 4 0 1 1-6.66-4.44l32-48a4 4 0 0 1 6.66 4.44M228 92a72.08 72.08 0 0 1-72 72h-25.86l-30.81 46.22a4 4 0 1 1-6.66-4.44L120.53 164H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 92m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 156h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
|
457 |
+ |
CatSnow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M84 196a8 8 0 1 1-8-8a8 8 0 0 1 8 8m32 8a8 8 0 1 0 8 8a8 8 0 0 0-8-8m48-16a8 8 0 1 0 8 8a8 8 0 0 0-8-8m-96 40a8 8 0 1 0 8 8a8 8 0 0 0-8-8m88 0a8 8 0 1 0 8 8a8 8 0 0 0-8-8m72-136a72.08 72.08 0 0 1-72 72H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 92m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 156h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
|
458 |
+ |
CatSleet: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M84 196a8 8 0 1 1-8-8a8 8 0 0 1 8 8m32 8a8 8 0 1 0 8 8a8 8 0 0 0-8-8m48-16a8 8 0 1 0 8 8a8 8 0 0 0-8-8m-96 40a8 8 0 1 0 8 8a8 8 0 0 0-8-8m88 0a8 8 0 1 0 8 8a8 8 0 0 0-8-8m72-136a72.08 72.08 0 0 1-72 72H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 92m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 156h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
|
459 |
+ |
CatFog: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE --><path fill="currentColor" d="M120 204H72a4 4 0 0 1 0-8h48a4 4 0 0 1 0 8m64-8h-24a4 4 0 0 0 0 8h24a4 4 0 0 0 0-8m-24 32h-56a4 4 0 0 0 0 8h56a4 4 0 0 0 0-8m68-128a72.08 72.08 0 0 1-72 72H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 100m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 164h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
|
460 |
+ |
} |