/* jshint esnext:true */ /* * * Weather extension for GNOME Shell * - Displays a small weather information on the top panel. * - On click, gives a popup with details about the weather. * * Copyright (C) 2011 - 2013 * ecyrbe , * Timur Kristof , * Elad Alfassa , * Simon Legner , * Christian METZLER , * Mark Benjamin weather.gnome.Markie1@dfgh.net, * Mattia Meneguzzo odysseus@fedoraproject.org, * Meng Zhuo , * Jens Lody * Copyright (C) 2014 -2018 * Jens Lody , * Copyright (C) 2018 * Taylor Raack * * * This file is part of gnome-shell-extension-openweather. * * gnome-shell-extension-openweather is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * gnome-shell-extension-openweather is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gnome-shell-extension-openweather. If not, see . * */ const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const OpenweathermapOrg = Me.imports.openweathermap_org; const Gettext = imports.gettext.domain('gnome-shell-extension-openweather'); const _ = Gettext.gettext; const ngettext = Gettext.ngettext; const OPENWEATHER_URL_HOST = 'api.openweathermap.org'; const OPENWEATHER_URL_BASE = 'https://' + OPENWEATHER_URL_HOST + '/data/2.5/'; const OPENWEATHER_URL_CURRENT = OPENWEATHER_URL_BASE + 'weather'; const OPENWEATHER_URL_FORECAST = OPENWEATHER_URL_BASE + 'forecast/daily'; function getWeatherIcon(code, night) { let iconname = ['weather-severe-alert']; // see http://bugs.openweathermap.org/projects/api/wiki/Weather_Condition_Codes // fallback icons are: weather-clear-night weather-clear weather-few-clouds-night weather-few-clouds weather-fog weather-overcast weather-severe-alert weather-showers weather-showers-scattered weather-snow weather-storm /* weather-clouds-night.png weather-freezing-rain.png weather-hail.png weather-many-clouds.png weather-showers-day.png weather-showers-night.png weather-showers-scattered-day.png weather-showers-scattered-night.png weather-snow-rain.png weather-snow-scattered-day.png weather-snow-scattered-night.png weather-snow-scattered.png weather-storm-day.png weather-storm-night.png weather-severe-alert-symbolic.svg weather-clear-night.png = weather-clear-night-symbolic.svg weather-clear.png = weather-clear-symbolic.svg weather-clouds.png = weather-overcast-symbolic.svg weather-few-clouds-night.png = weather-few-clouds-night-symbolic.svg weather-few-clouds.png = weather-few-clouds-symbolic.svg weather-mist.png = weather-fog-symbolic.svg weather-showers-scattered.png = weather-showers-scattered-symbolic.svg weather-showers.png = weather-showers-symbolic.svg weather-snow.png = weather-snow-symbolic.svg weather-storm.png = weather-storm-symbolic.svg */ switch (parseInt(code, 10)) { case 200: //Thunderstorm with light rain case 201: //Thunderstorm with rain case 202: //Thunderstorm with heavy rain case 210: //Light thunderstorm case 211: //Thunderstorm case 212: //Heavy thunderstorm case 221: //Ragged thunderstorm case 230: //Thunderstorm with light drizzle case 231: //Thunderstorm with drizzle case 232: //Thunderstorm with heavy drizzle iconname = ['weather-storm']; break; case 300: //Light intensity drizzle case 301: //drizzle case 302: //Heavy intensity drizzle case 310: //Light intensity drizzle rain case 311: //drizzle rain case 312: //Heavy intensity drizzle rain case 313: //Shower rain and drizzle case 314: //Heavy shower rain and drizzle case 321: //Shower drizzle iconname = ['weather-showers']; break; case 500: //Light rain case 501: //Moderate rain case 502: //Heavy intensity rain case 503: //Very heavy rain case 504: //Extreme rain iconname = ['weather-showers-scattered', 'weather-showers']; break; case 511: //Freezing rain iconname = ['weather-freezing-rain', 'weather-showers']; break; case 520: //Light intensity shower rain case 521: //Shower rain case 522: //Heavy intensity shower rain case 531: //Ragged shower rain iconname = ['weather-showers']; break; case 600: //Light snow case 601: //Snow case 602: //Heavy snow case 611: //Sleet case 612: //Shower sleet case 615: //Light rain and snow case 616: //Rain and snow case 620: //Light shower snow case 621: //Shower snow case 622: //Heavy shower snow iconname = ['weather-snow']; break; case 701: //Mist case 711: //Smoke case 721: //Haze case 741: //Fog iconname = ['weather-fog']; break; case 731: //Sand/Dust Whirls case 751: //Sand case 761: //Dust case 762: //VOLCANIC ASH case 771: //SQUALLS case 781: //TORNADO iconname = ['weather-severe-alert']; break; case 800: //Sky is clear iconname = ['weather-clear']; break; case 801: //Few clouds case 802: //Scattered clouds iconname = ['weather-few-clouds']; break; case 803: //Broken clouds iconname = ['weather-many-clouds', 'weather-overcast']; break; case 804: //Overcast clouds iconname = ['weather-overcast']; break; } for (let i = 0; i < iconname.length; i++) { if (night && this.hasIcon(iconname[i] + '-night')) return iconname[i] + '-night'; if (this.hasIcon(iconname[i])) return iconname[i]; } return 'weather-severe-alert'; } function getWeatherCondition(code) { switch (parseInt(code, 10)) { case 200: //Thunderstorm with light rain return _('Thunderstorm with light rain'); case 201: //Thunderstorm with rain return _('Thunderstorm with rain'); case 202: //Thunderstorm with heavy rain return _('Thunderstorm with heavy rain'); case 210: //Light thunderstorm return _('Light thunderstorm'); case 211: //Thunderstorm return _('Thunderstorm'); case 212: //Heavy thunderstorm return _('Heavy thunderstorm'); case 221: //Ragged thunderstorm return _('Ragged thunderstorm'); case 230: //Thunderstorm with light drizzle return _('Thunderstorm with light drizzle'); case 231: //Thunderstorm with drizzle return _('Thunderstorm with drizzle'); case 232: //Thunderstorm with heavy drizzle return _('Thunderstorm with heavy drizzle'); case 300: //Light intensity drizzle return _('Light intensity drizzle'); case 301: //Drizzle return _('Drizzle'); case 302: //Heavy intensity drizzle return _('Heavy intensity drizzle'); case 310: //Light intensity drizzle rain return _('Light intensity drizzle rain'); case 311: //Drizzle rain return _('Drizzle rain'); case 312: //Heavy intensity drizzle rain return _('Heavy intensity drizzle rain'); case 313: //Shower rain and drizzle return _('Shower rain and drizzle'); case 314: //Heavy shower rain and drizzle return _('Heavy shower rain and drizzle'); case 321: //Shower drizzle return _('Shower drizzle'); case 500: //Light rain return _('Light rain'); case 501: //Moderate rain return _('Moderate rain'); case 502: //Heavy intensity rain return _('Heavy intensity rain'); case 503: //Very heavy rain return _('Very heavy rain'); case 504: //Extreme rain return _('Extreme rain'); case 511: //Freezing rain return _('Freezing rain'); case 520: //Light intensity shower rain return _('Light intensity shower rain'); case 521: //Shower rain return _('Shower rain'); case 522: //Heavy intensity shower rain return _('Heavy intensity shower rain'); case 531: //Ragged shower rain return _('Ragged shower rain'); case 600: //Light snow return _('Light snow'); case 601: //Snow return _('Snow'); case 602: //Heavy snow return _('Heavy snow'); case 611: //Sleet return _('Sleet'); case 612: //Shower sleet return _('Shower sleet'); case 615: //Light rain and snow return _('Light rain and snow'); case 616: //Rain and snow return _('Rain and snow'); case 620: //Light shower snow return _('Light shower snow'); case 621: //Shower snow return _('Shower snow'); case 622: //Heavy shower snow return _('Heavy shower snow'); case 701: //Mist return _('Mist'); case 711: //Smoke return _('Smoke'); case 721: //Haze return _('Haze'); case 731: //Sand/Dust Whirls return _('Sand/Dust Whirls'); case 741: //Fog return _('Fog'); case 751: //Sand return _('Sand'); case 761: //Dust return _('Dust'); case 762: //VOLCANIC ASH return _('VOLCANIC ASH'); case 771: //SQUALLS return _('SQUALLS'); case 781: //TORNADO return _('TORNADO'); case 800: //Sky is clear return _('Sky is clear'); case 801: //Few clouds return _('Few clouds'); case 802: //Scattered clouds return _('Scattered clouds'); case 803: //Broken clouds return _('Broken clouds'); case 804: //Overcast clouds return _('Overcast clouds'); default: return _('Not available'); } } function parseWeatherCurrent() { if (this.currentWeatherCache === undefined) { // this is a reentrency guard this.currentWeatherCache = "in refresh"; this.refreshWeatherCurrent(); return; } if (this.currentWeatherCache == "in refresh") return; this.checkAlignment(); this.checkPositionInPanel(); let json = this.currentWeatherCache; this.owmCityId = json.id; // Refresh current weather let location = this.extractLocation(this._city); let comment = json.weather[0].description; if (this._translate_condition) comment = OpenweathermapOrg.getWeatherCondition(json.weather[0].id); let temperature = this.formatTemperature(json.main.temp); let sunrise = new Date(json.sys.sunrise * 1000); let sunset = new Date(json.sys.sunset * 1000); let now = new Date(); let iconname = this.getWeatherIcon(json.weather[0].id, now < sunrise || now > sunset); if (this.lastBuildId === undefined) this.lastBuildId = 0; if (this.lastBuildDate === undefined) this.lastBuildDate = 0; if (this.lastBuildId != json.dt || !this.lastBuildDate) { this.lastBuildId = json.dt; this.lastBuildDate = new Date(this.lastBuildId * 1000); } let lastBuild = '-'; if (this._clockFormat == "24h") { sunrise = sunrise.toLocaleFormat("%R"); sunset = sunset.toLocaleFormat("%R"); lastBuild = this.lastBuildDate.toLocaleFormat("%R"); } else { sunrise = sunrise.toLocaleFormat("%I:%M %p"); sunset = sunset.toLocaleFormat("%I:%M %p"); lastBuild = this.lastBuildDate.toLocaleFormat("%I:%M %p"); } let beginOfDay = new Date(new Date().setHours(0, 0, 0, 0)); let d = Math.floor((this.lastBuildDate.getTime() - beginOfDay.getTime()) / 86400000); if (d < 0) { lastBuild = _("Yesterday"); if (d < -1) lastBuild = ngettext("%d day ago", "%d days ago", -1 * d).format(-1 * d); } this._currentWeatherIcon.icon_name = this._weatherIcon.icon_name = iconname; let weatherInfoC = ""; let weatherInfoT = ""; if (this._comment_in_panel) weatherInfoC = comment; if (this._text_in_panel) weatherInfoT = temperature; this._weatherInfo.text = weatherInfoC + ((weatherInfoC && weatherInfoT) ? _(", ") : "") + weatherInfoT; this._currentWeatherSummary.text = comment + _(", ") + temperature; if (this._loc_len_current != 0 && location.length > this._loc_len_current) this._currentWeatherLocation.text = location.substring(0, (this._loc_len_current - 3)) + "..."; else this._currentWeatherLocation.text = location; this._currentWeatherCloudiness.text = json.clouds.all + ' %'; this._currentWeatherHumidity.text = json.main.humidity + ' %'; this._currentWeatherPressure.text = this.formatPressure(json.main.pressure); this._currentWeatherSunrise.text = sunrise; this._currentWeatherSunset.text = sunset; this._currentWeatherBuild.text = lastBuild; if (json.wind != undefined && json.wind.deg != undefined) { this._currentWeatherWind.text = this.formatWind(json.wind.speed, this.getWindDirection(json.wind.deg)); } else { this._currentWeatherWind.text = _("?"); } this.parseWeatherForecast(); this.recalcLayout(); } function refreshWeatherCurrent() { this.oldLocation = this.extractCoord(this._city); if (this.oldLocation.search(",") == -1) return; let params = { lat: this.oldLocation.split(",")[0], lon: this.oldLocation.split(",")[1], units: 'metric' }; if (this._appid) params.APPID = this._appid; this.load_json_async(OPENWEATHER_URL_CURRENT, params, function(json) { if (json && (Number(json.cod) == 200)) { if (this.currentWeatherCache != json) this.currentWeatherCache = json; this.rebuildSelectCityItem(); this.parseWeatherCurrent(); } else { // we are connected, but get no (or no correct) data, so try to reload // after 10 minutes (recommendded by openweathermap.org) this.reloadWeatherCurrent(600); } }); this.reloadWeatherCurrent(this._refresh_interval_current); } function parseWeatherForecast() { if (this.forecastWeatherCache === undefined) { // this is a reentrency guard this.forecastWeatherCache = "in refresh"; this.refreshWeatherForecast(); return; } if (this.forecastWeatherCache == "in refresh") return; let forecast = this.forecastWeatherCache; let beginOfDay = new Date(new Date().setHours(0, 0, 0, 0)); // OpenWeatherMap sometimes returns the previous day's forecast, especially in the early morning hours // of the lat / lng being queried. To prevent the first forecast element in the UI from being the previous // day's forecast, check for the returned forecast elements being for a previous day and maintain a // forecast index advance counter to skip previous day forecasts. let dateAdvanceIndex = 0; for (let i = 0; i < this._days_forecast; i++) { let forecastData = forecast[i]; if (forecastData === undefined) continue; let forecastDate = new Date(forecastData.dt * 1000).setHours(0,0,0,0); if (forecastDate >= beginOfDay) { // forecast is at least at the beginning of the current day; no need to look any further break; } // forecast is behind the current day, so advance the increment index dateAdvanceIndex++; } // Refresh forecast for (let i = 0; i < this._days_forecast; i++) { let forecastUi = this._forecast[i]; // make sure to use the dateAdvanceIndex to skip any previous day forecasts let forecastData = forecast[i + dateAdvanceIndex]; if (forecastData === undefined) continue; let t_low = this.formatTemperature(forecastData.temp.min); let t_high = this.formatTemperature(forecastData.temp.max); let comment = forecastData.weather[0].description; if (this._translate_condition) comment = OpenweathermapOrg.getWeatherCondition(forecastData.weather[0].id); let forecastDate = new Date(forecastData.dt * 1000); let dayLeft = Math.floor((forecastDate.getTime() - beginOfDay.getTime()) / 86400000); let date_string = _("Today"); if (dayLeft == 1) date_string = _("Tomorrow"); else if (dayLeft > 1) date_string = ngettext("In %d day", "In %d days", dayLeft).format(dayLeft); else if (dayLeft == -1) date_string = _("Yesterday"); else if (dayLeft < -1) date_string = ngettext("%d day ago", "%d days ago", -1 * dayLeft).format(-1 * dayLeft); forecastUi.Day.text = date_string + ' (' + this.getLocaleDay(forecastDate.getDay()) + ')\n' + forecastDate.toLocaleDateString(); forecastUi.Temperature.text = '\u2193 ' + t_low + ' \u2191 ' + t_high; forecastUi.Summary.text = comment; forecastUi.Icon.icon_name = this.getWeatherIcon(forecastData.weather[0].id); } } function refreshWeatherForecast() { this.oldLocation = this.extractCoord(this._city); if (this.oldLocation.search(",") == -1) return; let params = { lat: this.oldLocation.split(",")[0], lon: this.oldLocation.split(",")[1], units: 'metric', cnt: '13' }; if (this._appid) params.APPID = this._appid; this.load_json_async(OPENWEATHER_URL_FORECAST, params, function(json) { if (json && (Number(json.cod) == 200)) { if (this.forecastWeatherCache != json.list) { this.owmCityId = json.city.id; this.forecastWeatherCache = json.list; } this.parseWeatherForecast(); } else { // we are connected, but get no (or no correct) data, so try to reload // after 10 minutes (recommendded by openweathermap.org) this.reloadWeatherForecast(600); } }); this.reloadWeatherForecast(this._refresh_interval_forecast); }