mirror of
https://github.com/searxng/searxng.git
synced 2025-07-09 03:04:26 -04:00
[feat] wttr.in: migrate to new weather engine template (#4888)
Author Notes - wttr.in provides 8 hourly time forecasts per day, I assumed that they're always describing the weather for 3 hours each, starting at 1 o'clock in the morning related: - https://github.com/searxng/searxng/pull/4663 - https://github.com/searxng/searxng/issues/4885
This commit is contained in:
parent
99033f548e
commit
0cbb4f74cc
@ -1,9 +1,11 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
"""wttr.in (weather forecast service)"""
|
"""wttr.in (weather forecast service)"""
|
||||||
|
|
||||||
from json import loads
|
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from flask_babel import gettext
|
from datetime import datetime
|
||||||
|
|
||||||
|
from searx.result_types import EngineResults, WeatherAnswer
|
||||||
|
from searx import weather
|
||||||
|
|
||||||
about = {
|
about = {
|
||||||
"website": "https://wttr.in",
|
"website": "https://wttr.in",
|
||||||
@ -18,118 +20,105 @@ categories = ["weather"]
|
|||||||
|
|
||||||
url = "https://wttr.in/{query}?format=j1&lang={lang}"
|
url = "https://wttr.in/{query}?format=j1&lang={lang}"
|
||||||
|
|
||||||
|
# adapted from https://github.com/chubin/wttr.in/blob/master/lib/constants.py
|
||||||
def get_weather_condition_key(lang):
|
WWO_TO_CONDITION: dict[str, weather.WeatherConditionType] = {
|
||||||
if lang == "en":
|
"113": "clear sky",
|
||||||
return "weatherDesc"
|
"116": "partly cloudy",
|
||||||
|
"119": "cloudy",
|
||||||
return "lang_" + lang.lower()
|
"122": "fair",
|
||||||
|
"143": "fair",
|
||||||
|
"176": "light rain showers",
|
||||||
def generate_day_table(day):
|
"179": "light snow showers",
|
||||||
res = ""
|
"182": "light sleet showers",
|
||||||
|
"185": "light sleet",
|
||||||
res += f"<tr><td>{gettext('Average temp.')}</td><td>{day['avgtempC']}°C / {day['avgtempF']}°F</td></tr>"
|
"200": "rain and thunder",
|
||||||
res += f"<tr><td>{gettext('Min temp.')}</td><td>{day['mintempC']}°C / {day['mintempF']}°F</td></tr>"
|
"227": "light snow",
|
||||||
res += f"<tr><td>{gettext('Max temp.')}</td><td>{day['maxtempC']}°C / {day['maxtempF']}°F</td></tr>"
|
"230": "heavy snow",
|
||||||
res += f"<tr><td>{gettext('UV index')}</td><td>{day['uvIndex']}</td></tr>"
|
"248": "fog",
|
||||||
res += f"<tr><td>{gettext('Sunrise')}</td><td>{day['astronomy'][0]['sunrise']}</td></tr>"
|
"260": "fog",
|
||||||
res += f"<tr><td>{gettext('Sunset')}</td><td>{day['astronomy'][0]['sunset']}</td></tr>"
|
"263": "light rain showers",
|
||||||
|
"266": "light rain showers",
|
||||||
return res
|
"281": "light sleet showers",
|
||||||
|
"284": "light snow showers",
|
||||||
|
"293": "light rain showers",
|
||||||
def generate_condition_table(condition, lang, current=False):
|
"296": "light rain",
|
||||||
res = ""
|
"299": "rain showers",
|
||||||
|
"302": "rain",
|
||||||
if current:
|
"305": "heavy rain showers",
|
||||||
key = "temp_"
|
"308": "heavy rain",
|
||||||
else:
|
"311": "light sleet",
|
||||||
key = "temp"
|
"314": "sleet",
|
||||||
|
"317": "light sleet",
|
||||||
res += (
|
"320": "heavy sleet",
|
||||||
f"<tr><td><b>{gettext('Condition')}</b></td>"
|
"323": "light snow showers",
|
||||||
f"<td><b>{condition[get_weather_condition_key(lang)][0]['value']}</b></td></tr>"
|
"326": "light snow showers",
|
||||||
)
|
"329": "heavy snow showers",
|
||||||
res += (
|
"332": "heavy snow",
|
||||||
f"<tr><td><b>{gettext('Temperature')}</b></td>"
|
"335": "heavy snow showers",
|
||||||
f"<td><b>{condition[key+'C']}°C / {condition[key+'F']}°F</b></td></tr>"
|
"338": "heavy snow",
|
||||||
)
|
"350": "light sleet",
|
||||||
res += (
|
"353": "light rain showers",
|
||||||
f"<tr><td>{gettext('Feels like')}</td><td>{condition['FeelsLikeC']}°C / {condition['FeelsLikeF']}°F</td></tr>"
|
"356": "heavy rain showers",
|
||||||
)
|
"359": "heavy rain",
|
||||||
res += (
|
"362": "light sleet showers",
|
||||||
f"<tr><td>{gettext('Wind')}</td><td>{condition['winddir16Point']} — "
|
"365": "sleet showers",
|
||||||
f"{condition['windspeedKmph']} km/h / {condition['windspeedMiles']} mph</td></tr>"
|
"368": "light snow showers",
|
||||||
)
|
"371": "heavy snow showers",
|
||||||
res += (
|
"374": "light sleet showers",
|
||||||
f"<tr><td>{gettext('Visibility')}</td><td>{condition['visibility']} km / {condition['visibilityMiles']} mi</td>"
|
"377": "heavy sleet",
|
||||||
)
|
"386": "rain showers and thunder",
|
||||||
res += f"<tr><td>{gettext('Humidity')}</td><td>{condition['humidity']}%</td></tr>"
|
"389": "heavy rain showers and thunder",
|
||||||
|
"392": "snow showers and thunder",
|
||||||
return res
|
"395": "heavy snow showers",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def request(query, params):
|
def request(query, params):
|
||||||
if query.replace('/', '') in [":help", ":bash.function", ":translation"]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if params["language"] == "all":
|
|
||||||
params["language"] = "en"
|
|
||||||
else:
|
|
||||||
params["language"] = params["language"].split("-")[0]
|
|
||||||
|
|
||||||
params["url"] = url.format(query=quote(query), lang=params["language"])
|
params["url"] = url.format(query=quote(query), lang=params["language"])
|
||||||
|
|
||||||
params["raise_for_httperror"] = False
|
params["raise_for_httperror"] = False
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
def response(resp):
|
def _weather_data(location: weather.GeoLocation, data: dict):
|
||||||
results = []
|
# the naming between different data objects is inconsitent, thus temp_C and
|
||||||
|
# tempC are possible
|
||||||
|
tempC: float = data.get("temp_C") or data.get("tempC") # type: ignore
|
||||||
|
|
||||||
if resp.status_code == 404:
|
return WeatherAnswer.Item(
|
||||||
return []
|
location=location,
|
||||||
|
temperature=weather.Temperature(unit="°C", value=tempC),
|
||||||
result = loads(resp.text)
|
condition=WWO_TO_CONDITION[data["weatherCode"]],
|
||||||
|
feels_like=weather.Temperature(unit="°C", value=data["FeelsLikeC"]),
|
||||||
current = result["current_condition"][0]
|
wind_from=weather.Compass(int(data["winddirDegree"])),
|
||||||
location = result['nearest_area'][0]
|
wind_speed=weather.WindSpeed(data["windspeedKmph"], unit="km/h"),
|
||||||
|
pressure=weather.Pressure(data["pressure"], unit="hPa"),
|
||||||
forecast_indices = {3: gettext('Morning'), 4: gettext('Noon'), 6: gettext('Evening'), 7: gettext('Night')}
|
humidity=weather.RelativeHumidity(data["humidity"]),
|
||||||
|
cloud_cover=data["cloudcover"],
|
||||||
title = f"{location['areaName'][0]['value']}, {location['region'][0]['value']}"
|
|
||||||
|
|
||||||
infobox = f"<h3>{gettext('Current condition')}</h3><table><tbody>"
|
|
||||||
|
|
||||||
infobox += generate_condition_table(current, resp.search_params['language'], True)
|
|
||||||
|
|
||||||
infobox += "</tbody></table>"
|
|
||||||
|
|
||||||
for day in result["weather"]:
|
|
||||||
infobox += f"<h3>{day['date']}</h3>"
|
|
||||||
|
|
||||||
infobox += "<table><tbody>"
|
|
||||||
|
|
||||||
infobox += generate_day_table(day)
|
|
||||||
|
|
||||||
infobox += "</tbody></table>"
|
|
||||||
|
|
||||||
infobox += "<table><tbody>"
|
|
||||||
|
|
||||||
for time in forecast_indices.items():
|
|
||||||
infobox += f"<tr><td rowspan=\"7\"><b>{time[1]}</b></td></tr>"
|
|
||||||
|
|
||||||
infobox += generate_condition_table(day['hourly'][time[0]], resp.search_params['language'])
|
|
||||||
|
|
||||||
infobox += "</tbody></table>"
|
|
||||||
|
|
||||||
results.append(
|
|
||||||
{
|
|
||||||
"infobox": title,
|
|
||||||
"content": infobox,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return results
|
|
||||||
|
def response(resp):
|
||||||
|
res = EngineResults()
|
||||||
|
|
||||||
|
if resp.status_code == 404:
|
||||||
|
return res
|
||||||
|
|
||||||
|
json_data = resp.json()
|
||||||
|
geoloc = weather.GeoLocation.by_query(resp.search_params["query"])
|
||||||
|
|
||||||
|
weather_answer = WeatherAnswer(
|
||||||
|
current=_weather_data(geoloc, json_data["current_condition"][0]),
|
||||||
|
service="wttr.in",
|
||||||
|
)
|
||||||
|
|
||||||
|
for day in json_data["weather"]:
|
||||||
|
date = datetime.fromisoformat(day["date"])
|
||||||
|
time_slot_len = 24 // len(day["hourly"])
|
||||||
|
for index, forecast in enumerate(day["hourly"]):
|
||||||
|
forecast_data = _weather_data(geoloc, forecast)
|
||||||
|
forecast_data.datetime = weather.DateTime(date.replace(hour=index * time_slot_len + 1))
|
||||||
|
weather_answer.forecasts.append(forecast_data)
|
||||||
|
|
||||||
|
res.add(weather_answer)
|
||||||
|
return res
|
||||||
|
122
searx/weather.py
122
searx/weather.py
@ -481,46 +481,49 @@ class Compass:
|
|||||||
WeatherConditionType = typing.Literal[
|
WeatherConditionType = typing.Literal[
|
||||||
# The capitalized string goes into to i18n/l10n (en: "Clear sky" -> de: "wolkenloser Himmel")
|
# The capitalized string goes into to i18n/l10n (en: "Clear sky" -> de: "wolkenloser Himmel")
|
||||||
"clear sky",
|
"clear sky",
|
||||||
|
"partly cloudy",
|
||||||
"cloudy",
|
"cloudy",
|
||||||
"fair",
|
"fair",
|
||||||
"fog",
|
"fog",
|
||||||
"heavy rain and thunder",
|
# rain
|
||||||
"heavy rain showers and thunder",
|
|
||||||
"heavy rain showers",
|
|
||||||
"heavy rain",
|
|
||||||
"heavy sleet and thunder",
|
|
||||||
"heavy sleet showers and thunder",
|
|
||||||
"heavy sleet showers",
|
|
||||||
"heavy sleet",
|
|
||||||
"heavy snow and thunder",
|
|
||||||
"heavy snow showers and thunder",
|
|
||||||
"heavy snow showers",
|
|
||||||
"heavy snow",
|
|
||||||
"light rain and thunder",
|
"light rain and thunder",
|
||||||
"light rain showers and thunder",
|
"light rain showers and thunder",
|
||||||
"light rain showers",
|
"light rain showers",
|
||||||
"light rain",
|
"light rain",
|
||||||
"light sleet and thunder",
|
|
||||||
"light sleet showers and thunder",
|
|
||||||
"light sleet showers",
|
|
||||||
"light sleet",
|
|
||||||
"light snow and thunder",
|
|
||||||
"light snow showers and thunder",
|
|
||||||
"light snow showers",
|
|
||||||
"light snow",
|
|
||||||
"partly cloudy",
|
|
||||||
"rain and thunder",
|
"rain and thunder",
|
||||||
"rain showers and thunder",
|
"rain showers and thunder",
|
||||||
"rain showers",
|
"rain showers",
|
||||||
"rain",
|
"rain",
|
||||||
|
"heavy rain and thunder",
|
||||||
|
"heavy rain showers and thunder",
|
||||||
|
"heavy rain showers",
|
||||||
|
"heavy rain",
|
||||||
|
# sleet
|
||||||
|
"light sleet and thunder",
|
||||||
|
"light sleet showers and thunder",
|
||||||
|
"light sleet showers",
|
||||||
|
"light sleet",
|
||||||
"sleet and thunder",
|
"sleet and thunder",
|
||||||
"sleet showers and thunder",
|
"sleet showers and thunder",
|
||||||
"sleet showers",
|
"sleet showers",
|
||||||
"sleet",
|
"sleet",
|
||||||
|
"heavy sleet and thunder",
|
||||||
|
"heavy sleet showers and thunder",
|
||||||
|
"heavy sleet showers",
|
||||||
|
"heavy sleet",
|
||||||
|
# snow
|
||||||
|
"light snow and thunder",
|
||||||
|
"light snow showers and thunder",
|
||||||
|
"light snow showers",
|
||||||
|
"light snow",
|
||||||
"snow and thunder",
|
"snow and thunder",
|
||||||
"snow showers and thunder",
|
"snow showers and thunder",
|
||||||
"snow showers",
|
"snow showers",
|
||||||
"snow",
|
"snow",
|
||||||
|
"heavy snow and thunder",
|
||||||
|
"heavy snow showers and thunder",
|
||||||
|
"heavy snow showers",
|
||||||
|
"heavy snow",
|
||||||
]
|
]
|
||||||
"""Standardized designations for weather conditions. The designators were
|
"""Standardized designations for weather conditions. The designators were
|
||||||
taken from a collaboration between NRK and Norwegian Meteorological Institute
|
taken from a collaboration between NRK and Norwegian Meteorological Institute
|
||||||
@ -535,46 +538,49 @@ taken from a collaboration between NRK and Norwegian Meteorological Institute
|
|||||||
|
|
||||||
YR_WEATHER_SYMBOL_MAP = {
|
YR_WEATHER_SYMBOL_MAP = {
|
||||||
"clear sky": "01d", # 01d clearsky_day
|
"clear sky": "01d", # 01d clearsky_day
|
||||||
"fair": "02d", # 02d fair_day
|
|
||||||
"partly cloudy": "03d", # 03d partlycloudy_day
|
"partly cloudy": "03d", # 03d partlycloudy_day
|
||||||
"cloudy": "04", # 04 cloudy
|
"cloudy": "04", # 04 cloudy
|
||||||
"light rain showers": "40d", # 40d lightrainshowers_day
|
"fair": "02d", # 02d fair_day
|
||||||
"rain showers": "05d", # 05d rainshowers_day
|
|
||||||
"heavy rain showers": "41d", # 41d heavyrainshowers_day
|
|
||||||
"light rain showers and thunder": "24d", # 24d lightrainshowersandthunder_day
|
|
||||||
"rain showers and thunder": "06d", # 06d rainshowersandthunder_day
|
|
||||||
"heavy rain showers and thunder": "25d", # 25d heavyrainshowersandthunder_day
|
|
||||||
"light sleet showers": "42d", # 42d lightsleetshowers_day
|
|
||||||
"sleet showers": "07d", # 07d sleetshowers_day
|
|
||||||
"heavy sleet showers": "43d", # 43d heavysleetshowers_day
|
|
||||||
"light sleet showers and thunder": "26d", # 26d lightssleetshowersandthunder_day
|
|
||||||
"sleet showers and thunder": "20d", # 20d sleetshowersandthunder_day
|
|
||||||
"heavy sleet showers and thunder": "27d", # 27d heavysleetshowersandthunder_day
|
|
||||||
"light snow showers": "44d", # 44d lightsnowshowers_day
|
|
||||||
"snow showers": "08d", # 08d snowshowers_day
|
|
||||||
"heavy snow showers": "45d", # 45d heavysnowshowers_day
|
|
||||||
"light snow showers and thunder": "28d", # 28d lightssnowshowersandthunder_day
|
|
||||||
"snow showers and thunder": "21d", # 21d snowshowersandthunder_day
|
|
||||||
"heavy snow showers and thunder": "29d", # 29d heavysnowshowersandthunder_day
|
|
||||||
"light rain": "46", # 46 lightrain
|
|
||||||
"rain": "09", # 09 rain
|
|
||||||
"heavy rain": "10", # 10 heavyrain
|
|
||||||
"light rain and thunder": "30", # 30 lightrainandthunder
|
|
||||||
"rain and thunder": "22", # 22 rainandthunder
|
|
||||||
"heavy rain and thunder": "11", # 11 heavyrainandthunder
|
|
||||||
"light sleet": "47", # 47 lightsleet
|
|
||||||
"sleet": "12", # 12 sleet
|
|
||||||
"heavy sleet": "48", # 48 heavysleet
|
|
||||||
"light sleet and thunder": "31", # 31 lightsleetandthunder
|
|
||||||
"sleet and thunder": "23", # 23 sleetandthunder
|
|
||||||
"heavy sleet and thunder": "32", # 32 heavysleetandthunder
|
|
||||||
"light snow": "49", # 49 lightsnow
|
|
||||||
"snow": "13", # 13 snow
|
|
||||||
"heavy snow": "50", # 50 heavysnow
|
|
||||||
"light snow and thunder": "33", # 33 lightsnowandthunder
|
|
||||||
"snow and thunder": "14", # 14 snowandthunder
|
|
||||||
"heavy snow and thunder": "34", # 34 heavysnowandthunder
|
|
||||||
"fog": "15", # 15 fog
|
"fog": "15", # 15 fog
|
||||||
|
# rain
|
||||||
|
"light rain and thunder": "30", # 30 lightrainandthunder
|
||||||
|
"light rain showers and thunder": "24d", # 24d lightrainshowersandthunder_day
|
||||||
|
"light rain showers": "40d", # 40d lightrainshowers_day
|
||||||
|
"light rain": "46", # 46 lightrain
|
||||||
|
"rain and thunder": "22", # 22 rainandthunder
|
||||||
|
"rain showers and thunder": "06d", # 06d rainshowersandthunder_day
|
||||||
|
"rain showers": "05d", # 05d rainshowers_day
|
||||||
|
"rain": "09", # 09 rain
|
||||||
|
"heavy rain and thunder": "11", # 11 heavyrainandthunder
|
||||||
|
"heavy rain showers and thunder": "25d", # 25d heavyrainshowersandthunder_day
|
||||||
|
"heavy rain showers": "41d", # 41d heavyrainshowers_day
|
||||||
|
"heavy rain": "10", # 10 heavyrain
|
||||||
|
# sleet
|
||||||
|
"light sleet and thunder": "31", # 31 lightsleetandthunder
|
||||||
|
"light sleet showers and thunder": "26d", # 26d lightssleetshowersandthunder_day
|
||||||
|
"light sleet showers": "42d", # 42d lightsleetshowers_day
|
||||||
|
"light sleet": "47", # 47 lightsleet
|
||||||
|
"sleet and thunder": "23", # 23 sleetandthunder
|
||||||
|
"sleet showers and thunder": "20d", # 20d sleetshowersandthunder_day
|
||||||
|
"sleet showers": "07d", # 07d sleetshowers_day
|
||||||
|
"sleet": "12", # 12 sleet
|
||||||
|
"heavy sleet and thunder": "32", # 32 heavysleetandthunder
|
||||||
|
"heavy sleet showers and thunder": "27d", # 27d heavysleetshowersandthunder_day
|
||||||
|
"heavy sleet showers": "43d", # 43d heavysleetshowers_day
|
||||||
|
"heavy sleet": "48", # 48 heavysleet
|
||||||
|
# snow
|
||||||
|
"light snow and thunder": "33", # 33 lightsnowandthunder
|
||||||
|
"light snow showers and thunder": "28d", # 28d lightssnowshowersandthunder_day
|
||||||
|
"light snow showers": "44d", # 44d lightsnowshowers_day
|
||||||
|
"light snow": "49", # 49 lightsnow
|
||||||
|
"snow and thunder": "14", # 14 snowandthunder
|
||||||
|
"snow showers and thunder": "21d", # 21d snowshowersandthunder_day
|
||||||
|
"snow showers": "08d", # 08d snowshowers_day
|
||||||
|
"snow": "13", # 13 snow
|
||||||
|
"heavy snow and thunder": "34", # 34 heavysnowandthunder
|
||||||
|
"heavy snow showers and thunder": "29d", # 29d heavysnowshowersandthunder_day
|
||||||
|
"heavy snow showers": "45d", # 45d heavysnowshowers_day
|
||||||
|
"heavy snow": "50", # 50 heavysnow
|
||||||
}
|
}
|
||||||
"""Map a :py:obj:`WeatherConditionType` to a `YR weather symbol`_
|
"""Map a :py:obj:`WeatherConditionType` to a `YR weather symbol`_
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user