|
| 1 | +import json |
| 2 | + |
| 3 | +import requests |
| 4 | +from types import SimpleNamespace |
| 5 | +from gi.repository import Pango |
| 6 | + |
| 7 | +from util import settings |
| 8 | +from util.weather_types import APIUniqueField, BuiltinIcons, CustomIcons, Condition, Location, LocationData, WeatherData, WeatherProvider, Wind |
| 9 | + |
| 10 | +OWM_URL = "https://api.openweathermap.org/data/2.5/weather" |
| 11 | +OWM_SUPPORTED_LANGS = [ |
| 12 | + "af", "al", "ar", "az", "bg", "ca", "cz", "da", "de", "el", "en", "eu", "fa", "fi", |
| 13 | + "fr", "gl", "he", "hi", "hr", "hu", "id", "it", "ja", "kr", "la", "lt", "mk", "no", "nl", "pl", |
| 14 | + "pt", "pt_br", "ro", "ru", "se", "sk", "sl", "sp", "es", "sr", "th", "tr", "ua", "uk", "vi", "zh_cn", "zh_tw", "zu" |
| 15 | +] |
| 16 | + |
| 17 | + |
| 18 | +class OWMWeatherProvider(WeatherProvider): |
| 19 | + """ |
| 20 | + WeatherProvider implementation for OpenWeatherMap.org |
| 21 | + """ |
| 22 | + |
| 23 | + def __init__(self): |
| 24 | + self.needsApiKey = False |
| 25 | + self.prettyName = _("OpenWeatherMap") |
| 26 | + self.name = "OpenWeatherMap_Open" |
| 27 | + self.maxForecastSupport = 7 |
| 28 | + self.maxHourlyForecastSupport = 0 |
| 29 | + self.website = "https://openweathermap.org/" |
| 30 | + self.remainingCalls = None |
| 31 | + self.supportHourlyPrecipChance = False |
| 32 | + self.supportHourlyPrecipVolume = False |
| 33 | + |
| 34 | + # NOT SUPPORTED in cinnamon-spices-applets/weather |
| 35 | + # but it saves us implementing conversion functions until we need them |
| 36 | + # (which would be once we have more than just the one WeatherProvider) |
| 37 | + self.units = settings.get_weather_units() |
| 38 | + |
| 39 | + def GetWeather(self, loc: LocationData): |
| 40 | + lang = self.locale_to_owm_lang( |
| 41 | + Pango.language_get_default().to_string()) |
| 42 | + pref = Pango.language_get_preferred() |
| 43 | + if lang not in OWM_SUPPORTED_LANGS: |
| 44 | + for locale in pref: |
| 45 | + if self.locale_to_owm_lang(locale) in OWM_SUPPORTED_LANGS: |
| 46 | + lang = self.locale_to_owm_lang(locale) |
| 47 | + break |
| 48 | + # if we still have not found a supported language... |
| 49 | + if lang not in OWM_SUPPORTED_LANGS: |
| 50 | + lang = "en" |
| 51 | + |
| 52 | + response = requests.get(OWM_URL, |
| 53 | + { |
| 54 | + "lat": loc.lat, |
| 55 | + "lon": loc.lon, |
| 56 | + "units": self.units, |
| 57 | + "appid": settings.get_weather_api_key(), |
| 58 | + "lang": lang}) |
| 59 | + |
| 60 | + # actual object structure: https://github.com/linuxmint/cinnamon-spices-applets/weather@mockturtl/src/3_8/providers/openweathermap/payload/weather.ts |
| 61 | + data = json.loads( |
| 62 | + response.text, object_hook=lambda d: SimpleNamespace(**d)) |
| 63 | + print(data.name) |
| 64 | + return self.owm_data_to_weather_data(data) |
| 65 | + |
| 66 | + @staticmethod |
| 67 | + def locale_to_owm_lang(locale_string): |
| 68 | + if locale_string is None: |
| 69 | + return "en" |
| 70 | + |
| 71 | + # Dialect? support by OWM |
| 72 | + if locale_string == "zh-cn" or locale_string == "zh-cn" or locale_string == "pt-br": |
| 73 | + return locale_string |
| 74 | + |
| 75 | + lang = locale_string.split("-")[0] |
| 76 | + # OWM uses different language code for Swedish, Czech, Korean, Latvian, Norwegian |
| 77 | + if lang == "sv": |
| 78 | + return "se" |
| 79 | + elif lang == "cs": |
| 80 | + return "cz" |
| 81 | + elif lang == "ko": |
| 82 | + return "kr" |
| 83 | + elif lang == "lv": |
| 84 | + return "la" |
| 85 | + elif lang == "nn" or lang == "nb": |
| 86 | + return "no" |
| 87 | + return lang |
| 88 | + |
| 89 | + def owm_data_to_weather_data(self, owm_data) -> WeatherData: |
| 90 | + """ |
| 91 | + Returns as much of a complete WeatherData object as we can |
| 92 | + """ |
| 93 | + return WeatherData(**dict( |
| 94 | + date=owm_data.dt, |
| 95 | + sunrise=owm_data.sys.sunrise, |
| 96 | + sunset=owm_data.sys.sunset, |
| 97 | + coord=owm_data.coord, |
| 98 | + location=Location(**dict( |
| 99 | + city=owm_data.name, |
| 100 | + country=owm_data.sys.country, |
| 101 | + url="https://openweathermap.org/city/%s" % owm_data.id)), |
| 102 | + condition=Condition(**dict( |
| 103 | + main=owm_data.weather[0].main, |
| 104 | + description=owm_data.weather[0].description, |
| 105 | + icons=self.owm_icon_to_builtin_icons(owm_data.weather[0].icon), |
| 106 | + customIcon=self.owm_icon_to_custom_icon( |
| 107 | + owm_data.weather[0].icon) |
| 108 | + )), |
| 109 | + wind=Wind(**dict( |
| 110 | + speed=owm_data.wind.speed, |
| 111 | + degree=owm_data.wind.deg |
| 112 | + )), |
| 113 | + temperature=owm_data.main.temp, |
| 114 | + pressure=owm_data.main.pressure, |
| 115 | + humidity=owm_data.main.humidity, |
| 116 | + dewPoint=None, |
| 117 | + extra_field=APIUniqueField(**dict( |
| 118 | + type="temperature", |
| 119 | + name=_("Feels Like"), |
| 120 | + value=owm_data.main.feels_like |
| 121 | + )) |
| 122 | + )) |
| 123 | + |
| 124 | + def owm_icon_to_builtin_icons(self, icon) -> list[BuiltinIcons]: |
| 125 | + # https://openweathermap.org/weather-conditions |
| 126 | + # fallback icons are: weather-clear-night |
| 127 | + # weather-clear weather-few-clouds-night weather-few-clouds |
| 128 | + # weather-fog weather-overcast weather-severe-alert weather-showers |
| 129 | + # weather-showers-scattered weather-snow weather-storm |
| 130 | + match icon: |
| 131 | + case "10d": |
| 132 | + # rain day */ |
| 133 | + return ["weather-rain", "weather-showers-scattered", "weather-freezing-rain"] |
| 134 | + case "10n": |
| 135 | + # rain night */ |
| 136 | + return ["weather-rain", "weather-showers-scattered", "weather-freezing-rain"] |
| 137 | + case "09n": |
| 138 | + # showers night*/ |
| 139 | + return ["weather-showers"] |
| 140 | + case "09d": |
| 141 | + # showers day */ |
| 142 | + return ["weather-showers"] |
| 143 | + case "13d": |
| 144 | + # snow day*/ |
| 145 | + return ["weather-snow"] |
| 146 | + case "13n": |
| 147 | + # snow night */ |
| 148 | + return ["weather-snow"] |
| 149 | + case "50d": |
| 150 | + # mist day */ |
| 151 | + return ["weather-fog"] |
| 152 | + case "50n": |
| 153 | + # mist night */ |
| 154 | + return ["weather-fog"] |
| 155 | + case "04d": |
| 156 | + # broken clouds day */ |
| 157 | + return ["weather-overcast", "weather-clouds", "weather-few-clouds"] |
| 158 | + case "04n": |
| 159 | + # broken clouds night */ |
| 160 | + return ["weather-overcast", "weather-clouds-night", "weather-few-clouds-night"] |
| 161 | + case "03n": |
| 162 | + # mostly cloudy (night) */ |
| 163 | + return ['weather-clouds-night', "weather-few-clouds-night"] |
| 164 | + case "03d": |
| 165 | + # mostly cloudy (day) */ |
| 166 | + return ["weather-clouds", "weather-few-clouds", "weather-overcast"] |
| 167 | + case "02n": |
| 168 | + # partly cloudy (night) */ |
| 169 | + return ["weather-few-clouds-night"] |
| 170 | + case "02d": |
| 171 | + # partly cloudy (day) */ |
| 172 | + return ["weather-few-clouds"] |
| 173 | + case "01n": |
| 174 | + # clear (night) */ |
| 175 | + return ["weather-clear-night"] |
| 176 | + case "01d": |
| 177 | + # sunny */ |
| 178 | + return ["weather-clear"] |
| 179 | + case "11d": |
| 180 | + # storm day */ |
| 181 | + return ["weather-storm"] |
| 182 | + case "11n": |
| 183 | + # storm night */ |
| 184 | + return ["weather-storm"] |
| 185 | + case _: |
| 186 | + return ["weather-severe-alert"] |
| 187 | + |
| 188 | + @staticmethod |
| 189 | + def owm_icon_to_custom_icon(icon) -> CustomIcons: |
| 190 | + return None # TODO |
0 commit comments