#!/usr/bin/env python """ .. module:: Weather :synopsis: Fetch weather by location string .. moduleauthor:: Dave Pedu """ from pyircbot.modulebase import ModuleBase, command from requests import get from pyircbot.modules.ModInfo import info class Weather(ModuleBase): def __init__(self, bot, moduleName): ModuleBase.__init__(self, bot, moduleName) assert "get an API key" not in self.config["apikey"] self.login = self.bot.getBestModuleForService("login") try: assert self.login is not None except AssertionError as _ae: self.log.error("Weather: A 'login' service is required") return self.attr = self.bot.getBestModuleForService("attributes") try: assert self.attr is not None except AssertionError as _ae: # NOQA self.log.error("Weather: An 'attributes' service is required") return @info("weather [location]", "display the forecast", cmds=["weather", "w"]) @command("weather", "w") def cmd_weather(self, msg, cmd): hasUnit = self.attr.get(msg.prefix.nick, "weather-unit") if hasUnit: hasUnit = hasUnit.upper() if len(cmd.args_str) > 0: self.send_weather(msg.args[0], msg.prefix.nick, cmd.args_str, hasUnit) return weatherZip = self.attr.get(msg.prefix.nick, "weather-zip") if weatherZip is None: self.bot.act_PRIVMSG(msg.args[0], "%s: you must set a location with .setloc" % (msg.prefix.nick,)) return self.bot.act_PRIVMSG(msg.args[0], "%s: %s" % (msg.prefix.nick, self.getWeather(weatherZip, hasUnit))) @info("setloc ", "set your home location for weather lookups", cmds=["setloc"]) @command("setloc", allow_private=True) def cmd_setloc(self, msg, cmd): # if not cmd.args: # self.bot.act_PRIVMSG(fromWho, ".setloc: set your location for weather lookup. Example: " # ".setloc Rochester, NY") # return reply_to = msg.args[0] if msg.args[0].startswith("#") else msg.prefix.nick weatherLoc = cmd.args_str try: result = self.getWeather(weatherLoc) # NOQA except LocationNotSpecificException as lnse: self.bot.act_PRIVMSG(reply_to, "'%s': location not specific enough. Did you mean: %s" % (weatherLoc, self.alternates_to_str(lnse.alternates))) return except LocationException as le: self.bot.act_PRIVMSG(reply_to, "'%s': location not found: %s" % (weatherLoc, le)) return if not self.login.check(msg.prefix.nick, msg.prefix.hostname): self.bot.act_PRIVMSG(reply_to, ".setloc: you need to be logged in to do that (try .login)") return self.attr.set(msg.prefix.nick, "weather-zip", weatherLoc) self.bot.act_PRIVMSG(reply_to, "Saved your location as %s" % self.attr.get(msg.prefix.nick, "weather-zip")) if self.attr.get(msg.prefix.nick, "weather-zip") is None: self.bot.act_PRIVMSG(reply_to, "Tip: choose C or F with .wunit ") @info("wunit ", "set preferred weather unit", cmds=["wunit"]) @command("wunit", allow_private=True) def cmd_wunit(self, msg, cmd): if cmd.args[0].lower() not in ['c', 'f']: return unit = cmd.args[0].lower() reply_to = msg.args[0] if msg.args[0].startswith("#") else msg.prefix.nick # if unit is None: # self.bot.act_PRIVMSG(fromWho, ".wunit: set your preferred temperature unit to C or F") # return if not self.login.check(msg.prefix.nick, msg.prefix.hostname): self.bot.act_PRIVMSG(reply_to, ".wunit: you need to be logged in to do that (try .login)") return self.attr.set(msg.prefix.nick, "weather-unit", unit.lower()) self.bot.act_PRIVMSG(reply_to, "Saved your preferred unit as %s" % unit) def send_weather(self, target, hilight, location, units=None): try: self.bot.act_PRIVMSG(target, "%s: %s" % (hilight, self.getWeather(location, units))) except LocationNotSpecificException as lnse: self.bot.act_PRIVMSG(target, "'%s': location not specific enough. Did you mean: %s" % (location, self.alternates_to_str(lnse.alternates))) except LocationException as le: self.bot.act_PRIVMSG(target, "'%s': location not found: %s" % (location, le)) def alternates_to_str(self, alternates): pieces = [] for item in alternates: item_pieces = [] for key in ["name", "state", "country_name"]: if key in item and len(item[key].strip()): item_pieces.append(item[key]) pieces.append(', '.join(item_pieces)) return ' -- '.join(pieces) def getWeather(self, zipcode, unit=None): if unit is None: unit = self.config["defaultUnit"] unit = unit.lower() # Get data data = get("http://api.wunderground.com/api/%s/geolookup/conditions/forecast10day/q/%s.json" % (self.config["apikey"], zipcode)).json() if "results" in data["response"]: raise LocationNotSpecificException(data["response"]["results"]) if "error" in data["response"] and data["response"]["error"]["type"] == "querynotfound": raise LocationException # Build 5day fiveday = "" for item in data["forecast"]["simpleforecast"]["forecastday"][1:6]: fiveday += "%(day)s %(icon)s %(low)s-%(high)s°%(unit)s • " % { "unit": unit.upper(), "high": item["high"]["fahrenheit" if unit == "f" else "celsius"], "low": item["low"]["fahrenheit" if unit == "f" else "celsius"], "icon": self.icon2emoji(item["icon"]), "day": item["date"]["weekday_short"] } fiveday = fiveday[0:-3] # build wind speed wind_speed = data["current_observation"]["wind_mph"] if unit == "f" else data["current_observation"]["wind_kph"] wind_speed_gust = data["current_observation"]["wind_gust_mph"] if unit == "f" \ else data["current_observation"]["wind_gust_mph"] if not wind_speed == wind_speed_gust and float(wind_speed_gust) > 0: wind_speed = "%s-%s" % (wind_speed, wind_speed_gust) else: wind_speed = "%s" % (wind_speed,) # return message return "\x02%(city)s, %(state)s:\x02 %(sky)s, \x02%(temp)s°%(unit)s\x02. %(wind_str)s %(wind_speed)smph " \ "(%(wind_dir)s). \x02Next 5 days:\x02 %(fiveday)s" % { "city": data["current_observation"]["display_location"]["city"], "state": data["current_observation"]["display_location"]["state"], "sky": data["forecast"]["simpleforecast"]["forecastday"][0]["conditions"], "temp": int(data["current_observation"]["temp_f"]) if unit == "f" else int(data["current_observation"]["temp_c"]), "unit": unit.upper(), "wind_str": self.shorten_windstr(data["current_observation"]["wind_string"].lower()), "wind_speed": wind_speed, "wind_dir": self.deg_to_arrow(int(data["current_observation"]["wind_degrees"])), "fiveday": fiveday} def shorten_windstr(self, windstr): if "gusting" in windstr: return "Gusting" if "calm" in windstr: return "Calm" if "from the" in windstr.lower(): return "Varying" return windstr[0:12] def icon2emoji(self,icon): if "partlycloudy" in icon or "mostlycloudy" in icon: return "⛅️" elif "cloudy" in icon: return "☁️" elif "rain" in icon: return "💧" elif "clear" in icon: return "☀️" elif "snow" in icon: return "❄️" else: return "(%s)" % icon def deg_to_arrow(self, deg): if deg > 335 or deg < 0: return "↑" elif deg > 292: return "⇖" elif deg > 247: return "←" elif deg > 202: return "⇙" elif deg > 157: return "↓" elif deg > 112: return "⇘" elif deg > 67: return "→" elif deg > 22: return "⇗" class LocationException(Exception): pass class LocationNotSpecificException(LocationException): def __init__(self, alternates): self.alternates = alternates pass