.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
duckduckgo_weather.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""
3DuckDuckGo Weather
4~~~~~~~~~~~~~~~~~~
5"""
6from __future__ import annotations
7
8import typing as t
9from json import loads
10from urllib.parse import quote
11
12from dateutil import parser as date_parser
13
14from searx.engines.duckduckgo import fetch_traits # pylint: disable=unused-import
15from searx.engines.duckduckgo import get_ddg_lang
16from searx.enginelib.traits import EngineTraits
17
18from searx.result_types import EngineResults
19from searx.extended_types import SXNG_Response
20from searx import weather
21
22if t.TYPE_CHECKING:
23 import logging
24
25 logger: logging.Logger
26
27traits: EngineTraits
28
29
30about = {
31 "website": 'https://duckduckgo.com/',
32 "wikidata_id": 'Q12805',
33 "official_api_documentation": None,
34 "use_official_api": True,
35 "require_api_key": False,
36 "results": "JSON",
37}
38
39send_accept_language_header = True
40
41# engine dependent config
42categories = ["weather"]
43base_url = "https://duckduckgo.com/js/spice/forecast/{query}/{lang}"
44
45# adapted from https://gist.github.com/mikesprague/048a93b832e2862050356ca233ef4dc1
46WEATHERKIT_TO_CONDITION: dict[str, weather.WeatherConditionType] = {
47 "BlowingDust": "fog",
48 "Clear": "clear sky",
49 "Cloudy": "cloudy",
50 "Foggy": "fog",
51 "Haze": "fog",
52 "MostlyClear": "clear sky",
53 "MostlyCloudy": "partly cloudy",
54 "PartlyCloudy": "partly cloudy",
55 "Smoky": "fog",
56 "Breezy": "partly cloudy",
57 "Windy": "partly cloudy",
58 "Drizzle": "light rain",
59 "HeavyRain": "heavy rain",
60 "IsolatedThunderstorms": "rain and thunder",
61 "Rain": "rain",
62 "SunShowers": "rain",
63 "ScatteredThunderstorms": "heavy rain and thunder",
64 "StrongStorms": "heavy rain and thunder",
65 "Thunderstorms": "rain and thunder",
66 "Frigid": "clear sky",
67 "Hail": "heavy rain",
68 "Hot": "clear sky",
69 "Flurries": "light snow",
70 "Sleet": "sleet",
71 "Snow": "light snow",
72 "SunFlurries": "light snow",
73 "WintryMix": "sleet",
74 "Blizzard": "heavy snow",
75 "BlowingSnow": "heavy snow",
76 "FreezingDrizzle": "light sleet",
77 "FreezingRain": "sleet",
78 "HeavySnow": "heavy snow",
79 "Hurricane": "rain and thunder",
80 "TropicalStorm": "rain and thunder",
81}
82
83
84def _weather_data(location: weather.GeoLocation, data: dict[str, t.Any]):
85
86 return EngineResults.types.WeatherAnswer.Item(
87 location=location,
88 temperature=weather.Temperature(unit="°C", value=data['temperature']),
89 condition=WEATHERKIT_TO_CONDITION[data["conditionCode"]],
90 feels_like=weather.Temperature(unit="°C", value=data['temperatureApparent']),
91 wind_from=weather.Compass(data["windDirection"]),
92 wind_speed=weather.WindSpeed(data["windSpeed"], unit="mi/h"),
93 pressure=weather.Pressure(data["pressure"], unit="hPa"),
94 humidity=weather.RelativeHumidity(data["humidity"] * 100),
95 cloud_cover=data["cloudCover"] * 100,
96 )
97
98
99def request(query: str, params: dict[str, t.Any]):
100
101 eng_region = traits.get_region(params['searxng_locale'], traits.all_locale)
102 eng_lang = get_ddg_lang(traits, params['searxng_locale'])
103
104 # !ddw paris :es-AR --> {'ad': 'es_AR', 'ah': 'ar-es', 'l': 'ar-es'}
105 params['cookies']['ad'] = eng_lang
106 params['cookies']['ah'] = eng_region
107 params['cookies']['l'] = eng_region
108 logger.debug("cookies: %s", params['cookies'])
109
110 params["url"] = base_url.format(query=quote(query), lang=eng_lang.split('_')[0])
111 return params
112
113
114def response(resp: SXNG_Response):
115 res = EngineResults()
116
117 if resp.text.strip() == "ddg_spice_forecast();":
118 return res
119
120 json_data = loads(resp.text[resp.text.find('\n') + 1 : resp.text.rfind('\n') - 2])
121
122 geoloc = weather.GeoLocation.by_query(resp.search_params["query"])
123
124 weather_answer = EngineResults.types.WeatherAnswer(
125 current=_weather_data(geoloc, json_data["currentWeather"]),
126 service="duckduckgo weather",
127 )
128
129 for forecast in json_data['forecastHourly']['hours']:
130 forecast_time = date_parser.parse(forecast['forecastStart'])
131 forecast_data = _weather_data(geoloc, forecast)
132 forecast_data.datetime = weather.DateTime(forecast_time)
133 weather_answer.forecasts.append(forecast_data)
134
135 res.add(weather_answer)
136 return res
request(str query, dict[str, t.Any] params)
_weather_data(weather.GeoLocation location, dict[str, t.Any] data)