Skip to main content

max / balanced_breakfast

4.7 KB · 215 lines History Blame Raw
1 // National Weather Service alerts feed. Queries the NWS GeoJSON API for
2 // active alerts, filterable by state code and minimum severity level.
3 // Assembles area, urgency, certainty, and instructions into the body.
4
5 const NWS_API = "https://api.weather.gov/alerts/active";
6
7 fn id() {
8 "nws_alerts"
9 }
10
11 fn name() {
12 "NWS Weather Alerts"
13 }
14
15 fn capabilities() {
16 #{
17 supports_pagination: false
18 }
19 }
20
21 fn config_schema() {
22 #{
23 description: "Active weather alerts from the US National Weather Service. Filter by state or view all active alerts.",
24 fields: [
25 #{
26 key: "area",
27 label: "State/Territory",
28 field_type: "text",
29 description: "Two-letter state code (e.g., CA, NY, TX). Leave blank for all US alerts.",
30 placeholder: "CA"
31 },
32 #{
33 key: "severity",
34 label: "Minimum Severity",
35 field_type: "select",
36 required: true,
37 description: "Minimum alert severity to show",
38 default_value: "Moderate",
39 options: ["Extreme", "Severe", "Moderate", "Minor"]
40 }
41 ]
42 }
43 }
44
45 fn fetch(config, cursor) {
46 let url = NWS_API + "?status=actual";
47
48 if config.area != () && config.area != "" {
49 let area = str_trim(config.area);
50 if area.len() == 2 {
51 url += "&area=" + area;
52 }
53 }
54
55 let severity = "Moderate";
56 if config.severity != () {
57 severity = config.severity;
58 }
59 url += "&severity=" + severity;
60
61 let data = http_get_json(url);
62
63 if data == () {
64 return #{ items: [], has_more: false };
65 }
66
67 let features = data.features;
68 if features == () {
69 return #{ items: [], has_more: false };
70 }
71
72 let items = [];
73 for feature in features {
74 let item = parse_alert(feature);
75 if item != () {
76 items.push(item);
77 }
78 }
79
80 #{
81 items: items,
82 has_more: false
83 }
84 }
85
86 fn parse_alert(feature) {
87 let props = feature.properties;
88 if props == () {
89 return ();
90 }
91
92 let headline = "Weather Alert";
93 if props.headline != () {
94 headline = props.headline;
95 }
96
97 let event = "";
98 if props.event != () {
99 event = props.event;
100 }
101
102 let severity = "Unknown";
103 if props.severity != () {
104 severity = props.severity;
105 }
106
107 let urgency = "";
108 if props.urgency != () {
109 urgency = props.urgency;
110 }
111
112 let certainty = "";
113 if props.certainty != () {
114 certainty = props.certainty;
115 }
116
117 let description = "";
118 if props.description != () {
119 description = props.description;
120 }
121
122 let instruction = "";
123 if props.instruction != () {
124 instruction = props.instruction;
125 }
126
127 let area_desc = "";
128 if props.areaDesc != () {
129 area_desc = props.areaDesc;
130 }
131
132 let sender = "NWS";
133 if props.senderName != () {
134 sender = props.senderName;
135 }
136
137 let effective = timestamp_now();
138 if props.effective != () {
139 effective = props.effective;
140 }
141
142 let expires = "";
143 if props.expires != () {
144 expires = props.expires;
145 }
146
147 let alert_id = "";
148 if feature.id != () {
149 alert_id = feature.id;
150 } else if props.id != () {
151 alert_id = props.id;
152 }
153
154 // Build body
155 let body = "";
156 if area_desc != "" {
157 body += "**Area:** " + area_desc + "\n\n";
158 }
159 body += "**Severity:** " + severity;
160 if urgency != "" {
161 body += " · **Urgency:** " + urgency;
162 }
163 if certainty != "" {
164 body += " · **Certainty:** " + certainty;
165 }
166 body += "\n\n";
167
168 if description != "" {
169 body += description + "\n\n";
170 }
171 if instruction != "" {
172 body += "**Instructions:** " + instruction;
173 }
174
175 // Indicator based on severity
176 let indicator = "🌤";
177 if severity == "Extreme" {
178 indicator = "🔴";
179 } else if severity == "Severe" {
180 indicator = "🟠";
181 } else if severity == "Moderate" {
182 indicator = "🟡";
183 }
184
185 let secondary = severity;
186 if area_desc != "" {
187 secondary += " · " + truncate(area_desc, 60);
188 }
189
190 let tags = ["weather", "nws"];
191 if event != "" {
192 tags.push(event);
193 }
194
195 #{
196 id: #{ source: "nws_alerts", item_id: alert_id },
197 bite: #{
198 author: sender,
199 text: truncate(headline, 100),
200 secondary: truncate(secondary, 80),
201 indicator: indicator
202 },
203 content: #{
204 title: headline,
205 body: body,
206 url: ""
207 },
208 meta: #{
209 source_name: "NWS Alerts",
210 published_at: effective,
211 tags: tags
212 }
213 }
214 }
215