| 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 |
|