this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

New Weather Stuff

Ben C ef80b7d7 4750e991

+207 -117
+1 -1
homeModules/waybar.nix
··· 139 139 "custom/weather" = { 140 140 exec = "${pkgs.nushell}/bin/nu ${../res/custom_waybar_modules/weather.nu}"; 141 141 format = "{}"; 142 - interval = 600; 142 + interval = 900; 143 143 on-click = "xdg-open https://duckduckgo.com/?q=weather"; 144 144 return-type = "json"; 145 145 };
+202 -116
res/custom_waybar_modules/weather.nu
··· 1 1 #!/usr/bin/env nu 2 2 3 - # const WMO_CODE = { 4 - # 0: "Sunny", 5 - # 1: "PartlyCloudy", 6 - # 2: "Cloudy", 7 - # 3: "VeryCloudy", 8 - 9 - # } 3 + def search_loc [name: string] { 4 + let params = { 5 + name: $name, 6 + count: 1, 7 + language: "en", 8 + format: "json" 9 + } | url build-query; 10 + let endpoint = "https://geocoding-api.open-meteo.com/v1/search"; 11 + 12 + let resp = http get $"($endpoint)?($params)" | get -o results; 13 + 14 + if (($resp == null) or ($resp | is-empty)) { 15 + error make $"No geocode data for city: ($name)" 16 + } else { 17 + $resp | first | select latitude longitude 18 + } 19 + } 20 + 21 + def query_weather [loc: record<latitude: float, longitude: float>] { 22 + let loc_params = $loc | url build-query; 23 + let current_fields = [ 24 + temperature_2m 25 + apparent_temperature 26 + is_day 27 + precipitation 28 + relative_humidity_2m 29 + weather_code 30 + ] | str join ","; 31 + let hourly_fields = [ 32 + temperature_2m 33 + precipitation 34 + precipitation_probability 35 + weather_code 36 + is_day 37 + ] | str join ","; 38 + let daily_fields = [ 39 + sunrise 40 + sunset 41 + temperature_2m_max 42 + temperature_2m_min 43 + weather_code 44 + precipitation_probability_max 45 + ] | str join ","; 46 + 47 + let opt_params = { 48 + forecast_days: 7, 49 + forecast_hours: 12, 50 + wind_speed_unit: "mph", 51 + temperature_unit: "fahrenheit", 52 + precipitation_unit: "inch", 53 + timezone: "auto", 54 + current: $current_fields, 55 + hourly: $hourly_fields, 56 + daily: $daily_fields, 57 + } | url build-query; 58 + 59 + let params = $"($loc_params)&($opt_params)"; 60 + 61 + let endpoint = "https://api.open-meteo.com/v1/forecast"; 62 + 63 + http get $"($endpoint)?($params)" 64 + } 10 65 11 66 def match_wmo_code [code: int] { 12 67 match $code { ··· 30 85 } 31 86 } 32 87 33 - const WWO_CODE = { 34 - "113": "Sunny", 35 - "116": "PartlyCloudy", 36 - "119": "Cloudy", 37 - "122": "VeryCloudy", 38 - "143": "Fog", 39 - "176": "LightShowers", 40 - "179": "LightSleetShowers", 41 - "182": "LightSleet", 42 - "185": "LightSleet", 43 - "200": "ThunderyShowers", 44 - "227": "LightSnow", 45 - "230": "HeavySnow", 46 - "248": "Fog", 47 - "260": "Fog", 48 - "263": "LightShowers", 49 - "266": "LightRain", 50 - "281": "LightSleet", 51 - "284": "LightSleet", 52 - "293": "LightRain", 53 - "296": "LightRain", 54 - "299": "HeavyShowers", 55 - "302": "HeavyRain", 56 - "305": "HeavyShowers", 57 - "308": "HeavyRain", 58 - "311": "LightSleet", 59 - "314": "LightSleet", 60 - "317": "LightSleet", 61 - "320": "LightSnow", 62 - "323": "LightSnowShowers", 63 - "326": "LightSnowShowers", 64 - "329": "HeavySnow", 65 - "332": "HeavySnow", 66 - "335": "HeavySnowShowers", 67 - "338": "HeavySnow", 68 - "350": "LightSleet", 69 - "353": "LightShowers", 70 - "356": "HeavyShowers", 71 - "359": "HeavyRain", 72 - "362": "LightSleetShowers", 73 - "365": "LightSleetShowers", 74 - "368": "LightSnowShowers", 75 - "371": "HeavySnowShowers", 76 - "374": "LightSleetShowers", 77 - "377": "LightSleet", 78 - "386": "ThunderyShowers", 79 - "389": "ThunderyHeavyRain", 80 - "392": "ThunderySnowShowers", 81 - "395": "HeavySnowShowers", 82 - } 88 + const NIGHT_MAP = {"󰖙": "󰖔", "󰖕": "󰼱"}; 83 89 84 90 const WEATHER_ICONS = { 85 91 "Unknown": "󰨹", 92 + "Error": "󰧠", 86 93 "Cloudy": "󰖐", 87 94 "Fog": "󰖑", 88 95 "HeavyRain": "󰖖", ··· 100 107 "ThunderyHeavyRain": "󰙾", 101 108 "ThunderyShowers": "󰙾", 102 109 "ThunderySnowShowers": "󰙾", 103 - "VeryCloudy": "󰖐󰖐", 110 + "VeryCloudy": "", 111 + }; 112 + 113 + const WEATHER_DESCS = { 114 + "Unknown": "mysterious", 115 + "Cloudy": "cloudy", 116 + "Fog": "foggy", 117 + "HeavyRain": "heavily raining", 118 + "HeavyShowers": "heavily showering", 119 + "HeavySnow": "heavily snowing", 120 + "HeavySnowShowers": "heavily raining and snowing", 121 + "LightRain": "lightly raining", 122 + "LightShowers": "lightly showering", 123 + "LightSleet": "lightly sleeting", 124 + "LightSleetShowers": "lightly sleeting and raining", 125 + "LightSnow": "lightly snowing", 126 + "LightSnowShowers": "lightly snowing and showering", 127 + "PartlyCloudy": "partly cloudy", 128 + "Sunny": "sunny", 129 + "ThunderyHeavyRain": "heavily thunderstorming", 130 + "ThunderyShowers": "thunderstorming", 131 + "ThunderySnowShowers": "thunder and snow-storming", 132 + "VeryCloudy": "very cloudy", 133 + }; 134 + 135 + def get_icon [cond: string, is_day: int] { 136 + let icon = $WEATHER_ICONS | get -o $cond | default $WEATHER_ICONS.Unknown; 137 + 138 + if $is_day == 0 { 139 + $NIGHT_MAP | get -o $icon | default $icon 140 + } else { 141 + $icon 142 + } 104 143 } 105 144 106 - const NIGHT_MAP = {"󰖙": "󰖔", "󰖕": "󰼱"} 145 + def get_desc [cond: string] { 146 + $WEATHER_DESCS | get -o $cond | default $WEATHER_DESCS.Unknown 147 + } 107 148 108 - def is_night [astronomy: record] { 109 - let now_utc = date now 149 + def mk_condition [r: record, is_day: int] { 150 + let cond = match_wmo_code $r.weather_code; 110 151 111 - let sunrise = $astronomy.sunrise | into datetime 112 - let sunset = $astronomy.sunset | into datetime 152 + $"(get_icon $cond $is_day) (get_desc $cond)" 153 + } 113 154 114 - $now_utc > $sunset or $now_utc < $sunrise 155 + def evil_transpose [a: record] { 156 + 0..($a | values | get 0 | length | $in - 1) | each {|i| $a | columns | each {|c| [$c, ($a | get $c | get $i)]} | into record} 115 157 } 116 158 117 - def get_icon [condition: string, is_night: bool] { 118 - let icon = $WEATHER_ICONS | get -o $condition | default $WEATHER_ICONS.Unknown; 159 + def mk_text [weather: record] { 160 + let icon = get_icon (match_wmo_code $weather.current.weather_code) $weather.current.is_day; 161 + let temp = $weather.current.temperature_2m | math round; 162 + let unit = $weather.current_units.temperature_2m; 119 163 120 - if $is_night { 121 - $NIGHT_MAP | get -o $icon | default $icon 122 - } else { 123 - $icon 124 - } 164 + $"($icon) ($temp)($unit)" 125 165 } 126 166 127 - def error-exit [msg: string] { 128 - let out = { 129 - text: "󰧠", 130 - tooltip: $"Failed to fetch weather:\n($msg)", 131 - class: ["Unknown"] 132 - }; 133 - print ($out | to json -r) 134 - exit 167 + def mk_current_text [weather: record] { 168 + let c = $weather.current; 169 + let u = $weather.current_units; 170 + let cond = match_wmo_code $c.weather_code; 171 + let today = (evil_transpose $weather.daily) | first; 172 + 173 + [ 174 + $"󱃂 ($c.temperature_2m | math round)($u.temperature_2m) \(Feels like ($c.apparent_temperature | math round)($u.apparent_temperature)\)" 175 + $"(get_icon $cond $c.is_day) (get_desc $cond | str title-case)" 176 + $" ($c.precipitation) ($u.precipitation)(if $c.precipitation == 1 { '' } else { 'es' })" 177 + $" ($c.relative_humidity_2m)($u.relative_humidity_2m)" 178 + $"󰖜 ($today.sunrise | into datetime | format date '%I:%M %p')" 179 + $"󰖛 ($today.sunset | into datetime | format date '%I:%M %p')" 180 + ] | str join "\n" 135 181 } 136 182 137 - def main [] { 183 + def mk_hour_line [h: record, u: record] { 184 + let nice_time = $h.time | into datetime | format date "%_I %p"; 185 + let cond = match_wmo_code $h.weather_code; 186 + $"(get_icon $cond $h.is_day) ($nice_time) | ($h.temperature_2m | math round)($u.temperature_2m), ($h.precipitation_probability)($u.precipitation_probability)" 187 + } 138 188 139 - let raw = try { 140 - http get https://wttr.in/?format=j1 141 - } catch { |err| 142 - error-exit $err.msg; 143 - } 189 + def mk_hourly_text [weather: record] { 190 + let hours = evil_transpose $weather.hourly 191 + let u = $weather.hourly_units; 144 192 145 - if ((($raw | describe) == "string") and ($raw | str starts-with "Unknown location;")) { 146 - error-exit $raw; 147 - } 193 + $hours | skip 1 | each {mk_hour_line $in $u} | str join "\n" 194 + } 148 195 149 - let current_condition = $raw.current_condition.0 150 - let weather = $raw.weather.0 151 - let astronomy = $weather.astronomy.0 152 - let area = $raw.nearest_area.0 196 + def mk_daily_line [d: record, u: record] { 197 + let cond = match_wmo_code $d.weather_code; 198 + $"(get_icon $cond 1) ($d.time | into datetime | format date "%a"). | ($d.temperature_2m_min | math round)($u.temperature_2m_min) / ($d.temperature_2m_max | math round)($u.temperature_2m_max), ($d.precipitation_probability_max)($u.precipitation_probability_max)" 199 + } 200 + 201 + def mk_daily_text [weather: record] { 202 + let days = evil_transpose $weather.daily; 203 + let u = $weather.daily_units; 204 + 205 + $days | each {mk_daily_line $in $u} | str join "\n" 206 + } 207 + 208 + def mk_tooltip [weather: record, city: string] { 209 + [ 210 + $"<b>Current Weather in ($city)</b>" 211 + (mk_current_text $weather) 212 + " " 213 + "<b>Hourly Forecast</b>" 214 + (mk_hourly_text $weather) 215 + " " 216 + "<b>Daily Forecast</b>" 217 + (mk_daily_text $weather) 218 + ] | str join "\n" 219 + } 153 220 154 - let condition = $WWO_CODE | get -o $current_condition.weatherCode | default "Unknown" 221 + def mk_waybar_module [city: string] { 222 + let cache_loc_name = $"nu-waybar-loc-($city | hash sha256).json"; 223 + let cache_loc_path = $env | get -o "TMPDIR" | default "/tmp" | path join $cache_loc_name; 155 224 156 - let night = is_night $astronomy 225 + let loc = if ($cache_loc_path | path exists) { 226 + try { 227 + let cached_loc = open $cache_loc_path; 228 + if ($cached_loc | describe) != ({latitude: 0.0, longitude: 0.0} | describe) { 229 + error make "Invalid cache" 230 + } 231 + $cached_loc 232 + } catch { 233 + let new_loc = search_loc $city 234 + $new_loc | save -f $cache_loc_path 235 + $new_loc 236 + } 237 + } else { 238 + let new_loc = search_loc $city; 239 + $new_loc | save $cache_loc_path 240 + $new_loc 241 + }; 157 242 158 - let icon = get_icon $condition $night 243 + let w = query_weather $loc; 159 244 160 - let text = $"($icon) ($current_condition.temp_F)°F" 161 - let tooltip = [ 162 - $condition, 163 - $"Temperature: ($current_condition.temp_F) °F", 164 - $"Feels like ($current_condition.FeelsLikeF) °F", 165 - $"High of ($weather.maxtempF) °F / Low of ($weather.mintempF) °F", 166 - $"($current_condition.humidity)% Humidity", 167 - $"($current_condition.pressure) in. Pressure", 168 - $"($current_condition.uvIndex) UV Index", 169 - $"($current_condition.precipInches) in. Precipitation", 170 - $"($current_condition.visibilityMiles) mi. Visibility", 171 - $"($current_condition.windspeedMiles) mph Wind, Going ($current_condition.winddir16Point)", 172 - $"Sunrise: ($astronomy.sunrise) / Sunset: ($astronomy.sunset)", 173 - $"Location: ($area.areaName.0.value), ($area.region.0.value), ($area.country.0.value)", 174 - "Fetched from https://wttr.in" 175 - ] | str join "\n" 245 + { 246 + text: (mk_text $w), 247 + tooltip: (mk_tooltip $w $city), 248 + class: [(match_wmo_code $w.current.weather_code), (if $w.current.is_day == 1 { "day" } else { "night" })], 249 + } 250 + } 176 251 177 - let out = { 178 - text: $text, 179 - tooltip: $tooltip, 180 - class: [$condition, (if $night { "night" } else { "day" })] 252 + def mk_error_module [msg: string] { 253 + { 254 + text: $"($WEATHER_ICONS.Error) Err", 255 + tooltip: $"Failed to fetch weather\n\n($msg)", 256 + class: ["Error"], 181 257 } 258 + } 182 259 183 - print ($out | to json -r) 260 + def main [] { 261 + let city = http get "https://ipapi.co/json" | get city; 262 + 263 + let out = try { 264 + mk_waybar_module $city 265 + } catch {|err| 266 + mk_error_module $err.rendered 267 + }; 184 268 269 + $out | to json -r 185 270 } 271 +
+4
res/waybar.css
··· 113 113 margin-bottom: 0; 114 114 } 115 115 116 + #custom-weather.Error { 117 + border-color: @red; 118 + } 119 + 116 120 #custom-weather.VeryCloudy, 117 121 #custom-weather.Cloudy, 118 122 #custom-weather.Fog {