Massive refactor. Moved internal db over to sqlite. MUST READ README IF USING OLDER VERSION.

This commit is contained in:
spline 2013-06-09 16:34:22 -04:00
parent f435bcc004
commit 194ae52481
3 changed files with 356 additions and 334 deletions

View File

@ -4,40 +4,59 @@ Supybot-Weather
Overview
This is a Supybot plugin for displaying Weather via Weather Underground (http://www.wunderground.com)
They've got a nice JSON api that is free to use when you register and grab an API key. You will need
an API key to use this plugin. Configure it via:
/msg bot config plugin.Weather.apiKey <apiKey>
They've got a nice JSON api that is free to use when you register and grab an API key.
I made this plugin because quite a few Weather plugins didn't work well and WunderWeather, which uses
this API, is on their older XML api that they don't have documented anymore and, one would assume, will
be depreciated at some point.
There are a ton of options to configure. You can look through these via /msg <bot> config search Weather
Many of these are also available via --options when calling the wunderground command.
Besides a few of the ideas like having a user database, colorized temp, most of the code is mine.
Instructions
NOTICE: If you were using the older version of this plugin, you _MUST_ delete the older Weather.db
file in the Supybot data directory. Normally, this is at <supybotdir>/data/Weather.db
The internal DB is not compatable and must be deleted before.
First, you will need to register for a free API key. Signup takes less than a minute at:
http://www.wunderground.com/weather/api/
You will need an API key to use this plugin. Configure it via:
/msg <bot> config plugin.Weather.apiKey <apiKey>
Now reload the plugin:
/msg <bot> reload Weather
You can now use the basic functionality by:
/msg <bot> wunderground 10012 (or your zipcode)
I suggest adding an alias to this command to make it easier.
/msg bot Alias add weather wunderground
/msg <bot> Alias add weather wunderground
/msg <bot> Alias add w wunderground
Options
There are a ton of options to configure. You can look through these via /msg <bot> config search Weather
Many of these are also available via --help when calling the wunderground command.
Another feature that will make you and your users happy is an internal database that can remember your
location and setting for metric. I've seen this before with another bot and wanted to implement this.
location, setting for metric, and color temperature.
Basically, instead of having to type wunderground 10152 (or wherever you are), you can just type in
wunderground. This can be done via setting a location with the setweather command.
/msg <bot> setweather 10152
/msg <bot> setmetric False (to use imperial units)
/msg <bot> setcolortemp False (or true)
The bot's db is very simple and only remembers a nick and setting. So, if you change nicks, it will not
remember you unless you set it on this new nick.
Use:
/msg <bot> getweather
/msg <bot> getmetric
To check settings here. This is optional but a neat feature. This only works if you don't give it an input.
So, if you /msg bot wunderground --metric, it will display the weather you set in setweather but in --metric.
Options
This plugin has a bit of configuration that can be done with it. We'll start with the basics:
@ -57,30 +76,20 @@ Options
/msg <bot> config plugins.Weather.lang EN (replace EN with the 2 letter language code)
- disableANSI:
By default, ANSI is on. Color/bold on output makes things a bit easier to read.
If you do not want any color or bold in the output for a specific channel, you can:
/msg <bot> channel #channelname plugins.Weather.disableANSI True
or
/msg <bot> config plugins.Weather.disableANSI True
- disableColorTemp
On a similar note, I coded a neat "color" to temperature function that will color any temperature
on a basis of what it is (works for metric, too). Think of how temperature maps are done where
you would see red/orange/yellow if it's "hot", green if "moderate", and blue if its "cold".
By default, I have this ON. You can turn it off like this:
By default, I have this ON. This can also be personalized via /msg <bot> setcolortemp True/False
once a user is in the database. You can turn it off like this:
/msg <bot> config plugins.Weather.disableColorTemp True
Documentation
Some links:
- Main documentation: http://www.wunderground.com/weather/api/
# Main documentation: http://www.wunderground.com/weather/api/
# https://github.com/davidwilemski/Weather/blob/master/weather.py
# https://bitbucket.org/rizon/pypsd/src/8f975a375ab4/modules/internets/api/weather.py
# http://ronie.googlecode.com/svn-history/r283/trunk/weather.wunderground/default.py
# http://www.wunderground.com/weather/api/

View File

@ -21,8 +21,7 @@ def configure(advanced):
Weather = conf.registerPlugin('Weather')
conf.registerGlobalValue(Weather,'apiKey', registry.String('', ("""Your wunderground.com API key."""), private=True))
conf.registerChannelValue(Weather,'useImperial', registry.Boolean(True, ("""Use imperial units? Defaults to yes.""")))
conf.registerChannelValue(Weather,'disableANSI', registry.Boolean(False, """Do not display any ANSI (color/bold) for channel."""))
conf.registerChannelValue(Weather,'disableColoredTemp', registry.Boolean(False, """If disableANSI is True, this will color temperatures based on values."""))
conf.registerChannelValue(Weather,'disableColoredTemp', registry.Boolean(False, """If True, this will disable coloring temperatures based on values."""))
conf.registerChannelValue(Weather,'useWeatherSymbols', registry.Boolean(False, """Use unicode symbols with weather conditions and for wind direction."""))
conf.registerGlobalValue(Weather,'forecast', registry.Boolean(True, ("""Display forecast in output by default?""")))
conf.registerGlobalValue(Weather,'alerts', registry.Boolean(False, ("""Display alerts by default?""")))

564
plugin.py
View File

@ -3,19 +3,14 @@
# Copyright (c) 2012-2013, spline
# All rights reserved.
###
# my libs
import urllib2
import json
import re
from math import floor
from urllib import quote
from math import floor # for wind.
import sqlite3 # userdb.
import os
# extra supybot libs
import supybot.conf as conf
import supybot.ircdb as ircdb
import supybot.world as world
import supybot.log as log
# supybot libs
import supybot.utils as utils
from supybot.commands import *
@ -25,77 +20,113 @@ import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Weather')
# @internationalizeDocstring
@internationalizeDocstring
class WeatherDB(plugins.ChannelUserDB):
"""WeatherDB class to store our users locations and metric."""
def __init__(self, *args, **kwargs):
plugins.ChannelUserDB.__init__(self, *args, **kwargs)
class WeatherDB():
"""WeatherDB class to store our users and their settings."""
def serialize(self, v):
return list(v)
def __init__(self):
self.filename = conf.supybot.directories.data.dirize("Weather.db")
self.log = log.getPluginLogger('Weather')
self._conn = sqlite3.connect(self.filename, check_same_thread=False)
self._conn.text_factory = str
self.makeDb()
def deserialize(self, channel, id, L):
(id, metric) = L
return (id, metric)
def makeDb(self):
"""Create our DB."""
def getId(self, nick):
return self['x', nick.lower()][0]
self.log.info("WeatherDB: Checking/Creating DB.")
with self._conn as conn:
cursor = conn.cursor()
cursor.execute("""CREATE TABLE IF NOT EXISTS users (
nick TEXT PRIMARY KEY,
location TEXT NOT NULL,
metric INTEGER DEFAULT 0,
colortemp INTEGER DEFAULT 1)""")
self._conn.commit()
def getMetric(self, nick):
return self['x', nick.lower()][1]
def setweather(self, username, location):
"""Stores or update a user's location. Adds user if not found."""
with self._conn as conn:
cursor = conn.cursor()
if self.getuser(username): # username exists.
cursor.execute("""UPDATE users SET location=? WHERE nick=?""", (location, username,))
else: # username does not exist so add it in.
cursor.execute("""INSERT OR REPLACE INTO users (nick, location) VALUES (?,?)""", (username, location,))
self._conn.commit() # commit.
def setId(self, nick, id):
try:
metric = self['x', nick.lower()][1]
except KeyError:
metric = 'False'
self['x', nick.lower()] = (id, metric,)
def setmetric(self, username, metric):
"""Sets a user's metric value."""
with self._conn as conn:
cursor = conn.cursor()
cursor.execute("""UPDATE users SET metric=? WHERE nick=?""", (metric, username,))
self._conn.commit()
def setcolortemp(self, username, colortemp):
"""Sets a user's colortemp value."""
with self._conn as conn:
cursor = conn.cursor()
cursor.execute("""UPDATE users SET colortemp=? WHERE nick=?""", (colortemp, username))
self._conn.commit()
def getweather(self, user):
"""Return a dict of user's settings."""
self._conn.row_factory = sqlite3.Row
with self._conn as conn:
cursor = conn.cursor()
cursor.execute("""SELECT * from users where nick=?""", (user,))
row = cursor.fetchone()
if not row: # user does not exist.
return None
else: # user exists.
return row
def getuser(self, user):
"""Returns a boolean if a user exists."""
with self._conn as conn:
cursor = conn.cursor()
cursor.execute("""SELECT location from users where nick=?""", (user,))
row = cursor.fetchone()
if row:
return True
else:
return False
def setMetric(self, nick, metric):
try:
id = self['x', nick.lower()][0]
except:
id = '10121'
self['x', nick.lower()] = (id, metric,)
class Weather(callbacks.Plugin):
"""Add the help for "@plugin help Weather" here
This should describe *how* to use this plugin."""
threaded = True
# BASICS/WeatherDB
def __init__(self, irc):
self.__parent = super(Weather, self)
self.__parent.__init__(irc)
self.db = WeatherDB(conf.supybot.directories.data.dirize("Weather.db"))
self.APIKEY = self.registryValue('apiKey')
world.flushers.append(self.db.flush)
self.db = WeatherDB()
def die(self):
if self.db.flush in world.flushers:
world.flushers.remove(self.db.flush)
self.db.close()
self.__parent.die()
# COLORING
##############
# FORMATTING #
##############
def _bold(self, string):
return ircutils.bold(string)
def _bu(self, string):
return ircutils.underline(ircutils.bold(string))
def _blue(self, string):
return ircutils.mircColor(string, 'blue')
def _red(self, string):
return ircutils.mircColor(string, 'red')
def _strip(self, string):
return ircutils.stripFormatting(string)
# WEATHER SYMBOLS
############################
# INTERNAL WEATHER HELPERS #
############################
def _weatherSymbol(self, code):
"""Return a unicode symbol based on weather status."""
table = {'partlycloudy':'~☁',
'cloudy':'',
'tstorms':'',
@ -114,15 +145,17 @@ class Weather(callbacks.Plugin):
'chancesleet':'?❄',
'chancesnow':'?❄',
'chancetstorms':'?☔',
'unknown':''}
'unknown':'unknown'}
# return symbol from table.
try:
return table[code]
except KeyError:
return None
# MOON PHASE
def _moonphase(self, phase):
"""Returns a moon phase based on the %."""
# depending on the phase float, we have an ascii picture+text to represent it.
if phase < 0.05:
symbol = "[ ( ) ] (fullmoon)"
elif phase < 0.20:
@ -139,16 +172,18 @@ class Weather(callbacks.Plugin):
symbol = "[ D ] (half moon)"
else:
symbol = "[ D ] (waxing moon)"
# return.
return symbol
# COLOR TEMPERATURE
def _temp(self, x):
"""Returns a colored string based on the temperature."""
if x.endswith('C'):
x = float(str(x).replace('C','')) * 9 / 5 + 32
# first, convert into F so we only have one table.
if x.endswith('C'): # c.
x = float(str(x).replace('C', '')) * 9 / 5 + 32 # remove C + math into float(F).
unit = "C"
else:
x = float(str(x).replace('F',''))
else: # f.
x = float(str(x).replace('F', '')) # remove F. str->float.
unit = "F"
# determine color.
if x < 10.0:
@ -170,122 +205,113 @@ class Weather(callbacks.Plugin):
else:
color = 'light grey'
# return.
if unit == "F":
return ircutils.mircColor(("{0:.0f}F".format(x)),color)
else:
if unit == "F": # no need to convert back.
return ircutils.mircColor(("{0:.0f}F".format(x)), color)
else: # temp is in F and we need to go back to C.
return ircutils.mircColor(("{0:.0f}C".format((x - 32) * 5 / 9)),color)
# DEGREES TO DIRECTION (wind)
def _wind(self, angle, useSymbols=False):
if not useSymbols:
direction_names = ["N","NE","E","SE","S","SW","W","NW"]
else:
direction_names = ['','','','','','','','']
"""Converts degrees to direction for wind. Can optionally return a symbol."""
if not useSymbols: # ordinal names.
direction_names = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
else: # symbols.
direction_names = ['', '', '', '', '', '', '', '']
# do math below to figure the angle->direction out.
directions_num = len(direction_names)
directions_step = 360./directions_num
index = int(round((angle/360. - floor(angle/360.)*360.)/directions_step))
index %= directions_num
# return.
return direction_names[index]
# PUBLIC FUNCTIONS TO WORK WITH WEATHERDB.
def weatherusers(self, irc, msg, args):
"""
Returns the amount of users we know about.
"""
output = str(len(self.db.keys()))
irc.reply("I know about {0} users in my weather database.".format(str(output)))
weatherusers = wrap(weatherusers)
##############################################
# PUBLIC FUNCTIONS TO WORK WITH THE DATABASE #
##############################################
def setmetric(self, irc, msg, args, optboolean):
def setcolortemp(self, irc, msg, args, opttemp):
"""<True|False>
Sets the user's colortemp setting to True or False.
If True, will color temperature. If False, will not color.
"""
if opttemp: # handle opttemp
metric = 1
else: # False.
metric = 0
# check user first.
if self.db.getuser(msg.nick.lower()): # user exists
# perform op.
self.db.setcolortemp(msg.nick.lower(), metric)
irc.reply("I have changed {0}'s colortemp setting to {1}".format(msg.nick, metric))
else: # user is NOT In the database.
irc.reply("ERROR: You're not in the database. You must setweather first.")
setcolortemp = wrap(setcolortemp, [('boolean')])
def setmetric(self, irc, msg, args, optmetric):
"""<True|False>
Sets the user's use metric setting to True or False.
If True, will use netric. If False, will use imperial.
"""
# first, title case and cleanup as helper. Still must be True or False.
optboolean = optboolean.title().strip() # partial helpers.
if optboolean != "True" and optboolean != "False":
irc.reply("metric setting must be True or False")
return
# now, test if we have a username. setmetric for an unknown username = error
try:
self.db.getId(msg.nick)
except KeyError:
irc.reply("I have no user in the DB named {0}. Try setweather first.".format(msg.nick))
return
# now change it.
self.db.setMetric(msg.nick, optboolean)
irc.reply("I have changed {0}'s metric setting to {1}".format(msg.nick, optboolean))
setmetric = wrap(setmetric, [('somethingWithoutSpaces')])
def setweather(self, irc, msg, args, optid):
if optmetric: # handle opttemp
metric = 1
else: # False.
metric = 0
# check user first.
if self.db.getuser(msg.nick.lower()): # user exists
# perform op.
self.db.setmetric(msg.nick.lower(), metric)
irc.reply("I have changed {0}'s metric setting to {1}".format(msg.nick, metric))
else: # user is NOT In the database.
irc.reply("ERROR: You're not in the database. You must setweather first.")
setmetric = wrap(setmetric, [('boolean')])
def setweather(self, irc, msg, args, optlocation):
"""<location code>
Set's weather location code for your nick as <location code>.
Use your zip/postal code to keep it simple. Ex: setweather 03062
Set's weather location code for your nick as location code.
Use your zip/postal code to keep it simple.
Ex: setweather 10012
"""
# set the weather id based on nick.
optid = optid.replace(' ','')
self.db.setId(msg.nick, optid)
irc.reply("I have changed {0}'s weather ID to {1}".format(msg.nick, optid))
# set the weather id based on nick. This will update or set.
self.db.setweather(msg.nick.lower(), optlocation)
irc.reply("I have changed {0}'s weather ID to {1}".format(msg.nick.lower(), optlocation))
setweather = wrap(setweather, [('text')])
def getmetric(self, irc, msg, args, optnick):
"""[nick]
Get the metric setting of your or [nick].
"""
# allows us to specify the nick.
if not optnick:
optnick = msg.nick
# now try to fetch the metric. Tests if we have a username.
try:
irc.reply("The metric setting for {0} is {1}".format(optnick, self.db.getMetric(optnick)))
except KeyError:
irc.reply('I have no weather metric setting for {0}'.format(optnick))
getmetric = wrap(getmetric, [optional('somethingWithoutSpaces')])
def getweather(self, irc, msg, args, optnick):
"""[nick]
Get the weather ID of your or [nick].
"""
# allow us to specify the nick if we don't have it.
if not optnick:
optnick = msg.nick
# now try and fetch the metric setting. error if it's broken.
try:
irc.reply("The weather ID for {0} is {1}".format(optnick, self.db.getId(optnick)))
except KeyError:
irc.reply('I have no weather ID for %s.' % optnick)
getweather = wrap(getweather, [optional('somethingWithoutSpaces')])
# CHECK FOR API KEY. (NOT PUBLIC)
def keycheck(self, irc):
"""Check and make sure we have an API key."""
if len(self.APIKEY) < 1 or not self.APIKEY or self.APIKEY == "Not set":
irc.reply("ERROR: Need a Wunderground API key. Set config plugins.Weather.apiKey.")
return False
else:
return True
####################
# PUBLIC FUNCTIONS #
####################
def wunderground(self, irc, msg, args, optlist, optinput):
"""[--options] [location]
"""[--options] <location>
Fetch weather and forcast information for <location>
Location must be one of: US state/city (CA/San_Francisco), zipcode, country/city (Australia/Sydney), airport code (KJFK)
For options:
Use --help to list all options.
Ex: 10021 or Sydney, Australia or KJFK
"""
# first, check if we have an API key. Useless w/o this.
if not self.keycheck(irc):
return False
if len(self.APIKEY) < 1 or not self.APIKEY or self.APIKEY == "Not set":
irc.reply("ERROR: Need a Wunderground API key. Set config plugins.Weather.apiKey and reload Weather.")
return
# urlargs will be used to build the url to query the API.
urlArgs = {'features':['conditions','forecast'],
urlArgs = {'features':['conditions', 'forecast'],
'lang':self.registryValue('lang'),
'bestfct':'1',
'pws':'0'
}
'pws':'0' }
# now, start our dict for output formatting.
args = {'imperial':self.registryValue('useImperial', msg.args[0]),
'nocolortemp':self.registryValue('disableColoredTemp', msg.args[0]),
'alerts':self.registryValue('alerts'),
'almanac':self.registryValue('almanac'),
'astronomy':self.registryValue('astronomy'),
@ -296,23 +322,7 @@ class Weather(callbacks.Plugin):
'strip':False,
'uv':False,
'visibility':False,
'dewpoint':False
}
# now check if we have a location. if no location, use the userdb. also set for metric variable.
# autoip.json?geo_ip=38.102.136.138
if not optinput:
try:
optinput = self.db.getId(msg.nick)
optmetric = self.db.getMetric(msg.nick) # set our imperial units here.
if optmetric == "True":
args['imperial'] = False
else:
args['imperial'] = True
except KeyError:
irc.reply("I did not find a preset location for you. Set via: setweather location or specify a location")
return
'dewpoint':False }
# handle optlist (getopts). this will manipulate output via args dict.
if optlist:
for (key, value) in optlist:
@ -336,9 +346,33 @@ class Weather(callbacks.Plugin):
args['dewpoint'] = True
if key == 'astronomy':
args['astronomy'] = True
if key == 'nocolortemp':
args['nocolortemp'] = True
if key == 'help': # make shift help because the docstring is overloaded above.
irc.reply("Options: --metric --alerts --forecast --almanac --pressure --wind --uv --visibility --dewpoint --astronomy --nocolortemp")
irc.reply("WeatherDB options: setweather <location> (set user's location). setmetric True/False (set metric option) setcolortemp True/False (display color temp?")
return
# now check if we have a location. if no location, use the WeatherDB.
if not optinput: # no location on input.
userloc = self.db.getweather(msg.nick.lower()) # check the db.
if userloc: # found a user so we change args w/info.
optinput = userloc['location'] # grab location.
# setmetric.
if userloc['metric'] == 0: # 0 = False for metric.
args['imperial'] = True # so we make sure we're using imperial.
elif userloc['metric'] == 1: # do the inverse.
args['imperial'] = False
# setcolortemp.
if userloc['colortemp'] == 0: # 0 = False for colortemp.
args['nocolortemp'] = True # disable
elif userloc['colortemp'] == 1: # do the inverse.
args['nocolortemp'] = False # show color temp.
else: # no user NOR optinput found. error msg.
irc.reply("ERROR: I did not find a preset location for you. Set via setweather <location>")
return
# build url now. first, apikey. then, iterate over urlArgs and insert.
# urlArgs['features'] also manipulated via what's in args.
url = 'http://api.wunderground.com/api/%s/' % (self.APIKEY) # first part of url, w/APIKEY
# now we need to set certain things for urlArgs based on args.
for check in ['alerts','almanac','astronomy']:
@ -351,42 +385,36 @@ class Weather(callbacks.Plugin):
if key == "lang" or key == "bestfct" or key == "pws": # rest added with key:value
url += "{0}:{1}/".format(key, value)
# finally, attach the q/input. url is now done.
url += 'q/%s.json' % quote(optinput)
url += 'q/%s.json' % utils.web.urlquote(optinput)
#self.log.info(url)
# try and query.
# try and query url.
try:
request = urllib2.Request(url)
u = urllib2.urlopen(request)
except Exception as e:
self.log.info("Error loading {0} message {1}".format(url, e))
irc.reply("Failed to load wunderground API: %s" % e)
page = utils.web.getUrl(url)
except utils.web.Error as e:
self.log.error("ERROR: Trying to open {0} message: {1}".format(url, e))
irc.reply("ERROR: Failed to load Weather Underground API: {0}".format(e))
return
# process the json, check (in orders) for errors, multiple results, and one last
# sanity check. then we can process it.
data = json.load(u)
# process json.
data = json.loads(page.decode('utf-8'))
# check if we got errors and return.
if 'error' in data['response']:
errortype = data['response']['error']['type']
# now, a series of sanity checks before we process.
if 'error' in data['response']: # check if there are errors.
errortype = data['response']['error']['type'] # type. description is below.
errordesc = data['response']['error'].get('description', 'no description')
irc.reply("{0} I got an error searching for {1}. ({2}: {3})".format(self._red("ERROR:"), optinput, errortype, errordesc))
irc.reply("ERROR: I got an error searching for {0}. ({1}: {2})".format(optinput, errortype, errordesc))
return
# if there is more than one city matching.
if 'results' in data['response']:
output = [item['city'] + ", " + item['state'] + " (" + item['country_name'] + ")" for item in data['response']['results']]
irc.reply("More than 1 city matched your query, try being more specific: {0}".format(" | ".join(output)))
if 'results' in data['response']: # results only comes when location matches more than one.
output = [i['city'] + ", " + i['state'] + " (" + i['country_name'] + ")" for i in data['response']['results']]
irc.reply("ERROR: More than 1 city matched your query, try being more specific: {0}".format(" | ".join(output)))
return
# last sanity check
if not data.has_key('current_observation'):
irc.reply("{0} something went horribly wrong looking up weather for {1}. Contact the plugin owner.".format(self._red("ERROR:"), optinput))
irc.reply("ERROR: something went horribly wrong looking up weather for {0}. Contact the plugin owner.".format(optinput))
return
# done with error checking.
# now, put everything into outdata dict for output later.
# no errors so we start the main part of processing.
outdata = {}
outdata['weather'] = data['current_observation']['weather']
outdata['location'] = data['current_observation']['display_location']['full']
@ -394,28 +422,26 @@ class Weather(callbacks.Plugin):
outdata['uv'] = data['current_observation']['UV']
# handle wind. check if there is none first.
if args['imperial']:
if data['current_observation']['wind_mph'] < 1: # no wind.
outdata['wind'] = "None"
else:
outdata['wind'] = "{0}@{1}mph".format(self._wind(data['current_observation']['wind_degrees']),data['current_observation']['wind_mph'])
if data['current_observation']['wind_gust_mph'] > 0:
else: # we do have wind. process differently.
if args['imperial']: # imperial units for wind.
outdata['wind'] = "{0}@{1}mph".format(self._wind(data['current_observation']['wind_degrees']), data['current_observation']['wind_mph'])
if data['current_observation']['wind_gust_mph'] > 0: # gusts?
outdata['wind'] += " ({0}mph gusts)".format(data['current_observation']['wind_gust_mph'])
else:
if data['current_observation']['wind_kph'] < 1: # no wind.
outdata['wind'] = "None"
else:
else: # handle metric units for wind.
outdata['wind'] = "{0}@{1}kph".format(self._wind(data['current_observation']['wind_degrees']),data['current_observation']['wind_kph'])
if data['current_observation']['wind_gust_kph'] > 0:
if data['current_observation']['wind_gust_kph'] > 0: # gusts?
outdata['wind'] += " ({0}kph gusts)".format(data['current_observation']['wind_gust_kph'])
# handle the time. concept/method from WunderWeather plugin.
observationTime = data['current_observation'].get('observation_epoch', None)
localTime = data['current_observation'].get('local_epoch', None)
if not observationTime or not localTime: # if we don't have the epoches from above, default to obs_time
observationTime = data['current_observation'].get('observation_epoch')
localTime = data['current_observation'].get('local_epoch')
# if we don't have the epoches from above, default to obs_time
if not observationTime or not localTime:
outdata['observation'] = data.get('observation_time', 'unknown').lstrip('Last Updated on ')
else: # format for relative time.
s = int(localTime) - int(observationTime)
else: # we do have so format for relative time.
s = int(localTime) - int(observationTime) # format into seconds.
if s <= 1:
outdata['observation'] = 'just now'
elif s < 60:
@ -429,8 +455,8 @@ class Weather(callbacks.Plugin):
else:
outdata['observation'] = '{0}hrs ago'.format(s/3600)
# all conditionals for imperial/metric
if args['imperial']:
# handle basics like temp/pressure/dewpoint.
if args['imperial']: # assigns the symbol based on metric.
outdata['temp'] = str(data['current_observation']['temp_f']) + 'F'
outdata['pressure'] = data['current_observation']['pressure_in'] + 'in'
outdata['dewpoint'] = str(data['current_observation']['dewpoint_f']) + 'F'
@ -438,7 +464,7 @@ class Weather(callbacks.Plugin):
outdata['windchill'] = str(data['current_observation']['windchill_f']) + 'F'
outdata['feelslike'] = str(data['current_observation']['feelslike_f']) + 'F'
outdata['visibility'] = str(data['current_observation']['visibility_mi']) + 'mi'
else:
else: # metric.
outdata['temp'] = str(data['current_observation']['temp_c']) + 'C'
outdata['pressure'] = data['current_observation']['pressure_mb'] + 'mb'
outdata['dewpoint'] = str(data['current_observation']['dewpoint_c']) + 'C'
@ -447,31 +473,30 @@ class Weather(callbacks.Plugin):
outdata['feelslike'] = str(data['current_observation']['feelslike_c']) + 'C'
outdata['visibility'] = str(data['current_observation']['visibility_km']) + 'km'
# handle forecast data part. output will be below.
# this is not the --forecast part.
forecastdata = {}
# handle forecast data part. output will be below. (not --forecast)
forecastdata = {} # key = int(day), value = forecast dict.
for forecastday in data['forecast']['txt_forecast']['forecastday']:
tmpdict = {}
tmpdict['day'] = forecastday['title']
tmpdict['symbol'] = self._weatherSymbol(forecastday['icon'])
if args['imperial']:
if args['imperial']: # imperial.
tmpdict['text'] = forecastday['fcttext']
else:
else: # metric.
tmpdict['text'] = forecastday['fcttext_metric']
forecastdata[int(forecastday['period'])] = tmpdict
# now this is the --forecast part.
if args['forecast']:
fullforecastdata = {}
if args['forecast']: # only if we get this in getopts.
fullforecastdata = {} # key = day (int), value = dict of forecast data.
for forecastday in data['forecast']['simpleforecast']['forecastday']:
tmpdict = {}
tmpdict['day'] = forecastday['date']['weekday_short']
tmpdict['symbol'] = self._weatherSymbol(forecastday['icon'])
tmpdict['text'] = forecastday['conditions']
if args['imperial']:
if args['imperial']: # imperial.
tmpdict['high'] = forecastday['high']['fahrenheit'] + "F"
tmpdict['low'] = forecastday['low']['fahrenheit'] + "F"
else:
else: # metric.
tmpdict['high'] = forecastday['high']['celsius'] + "C"
tmpdict['low'] = forecastday['low']['celsius'] + "C"
fullforecastdata[int(forecastday['period'])] = tmpdict
@ -480,12 +505,12 @@ class Weather(callbacks.Plugin):
if args['almanac']:
outdata['highyear'] = data['almanac']['temp_high']['recordyear']
outdata['lowyear'] = data['almanac']['temp_low']['recordyear']
if args['imperial']:
if args['imperial']: # imperial.
outdata['highnormal'] = data['almanac']['temp_high']['normal']['F'] + "F"
outdata['highrecord'] = data['almanac']['temp_high']['record']['F'] + "F"
outdata['lownormal'] = data['almanac']['temp_low']['normal']['F'] + "F"
outdata['lowrecord'] = data['almanac']['temp_low']['record']['F'] + "F"
else:
else: # metric.
outdata['highnormal'] = data['almanac']['temp_high']['normal']['C'] + "C"
outdata['highrecord'] = data['almanac']['temp_high']['record']['C'] + "C"
outdata['lownormal'] = data['almanac']['temp_low']['normal']['C'] + "C"
@ -499,99 +524,86 @@ class Weather(callbacks.Plugin):
sunrisem = int(data['moon_phase']['sunrise']['minute'])
sunseth = int(data['moon_phase']['sunset']['hour'])
sunsetm = int(data['moon_phase']['sunset']['minute'])
outdata['sunrise'] = "{0}:{1}".format(sunriseh,sunrisem)
outdata['sunset'] = "{0}:{1}".format(sunseth,sunsetm)
outdata['sunrise'] = "{0}:{1}".format(sunriseh, sunrisem) # construct sunrise.
outdata['sunset'] = "{0}:{1}".format(sunseth, sunsetm) # construct sunset. calc "time of day" below.
outdata['lengthofday'] = "%dh%dm" % divmod((((sunseth-sunriseh)+float((sunsetm-sunrisem)/60.0))*60),60)
# handle alerts
if args['alerts']:
if data['alerts']:
outdata['alerts'] = data['alerts'][:300] # alert limit to 300.
else:
if args['alerts']: # only look for alerts if there.
if data['alerts']: # alerts is a list. it can also be empty.
outdata['alerts'] = data['alerts'][:300] # limit chars to 300.
else: # no alerts found (empty).
outdata['alerts'] = "No alerts."
# OUTPUT
# now, build output object with what to output. ° u" \u00B0C"
if self.registryValue('disableColoredTemp'):
output = "Weather for {0} :: {1} ({2})".format(self._bold(outdata['location']),\
outdata['weather'],outdata['temp'])
else:
output = "Weather for {0} :: {1} ({2})".format(self._bold(outdata['location']),\
outdata['weather'],self._temp(outdata['temp']))
# OUTPUT.
# we go step-by-step to build the proper string. ° u" \u00B0C"
output = "Weather for {0} :: {1}".format(self._bold(outdata['location'].encode('utf-8')), outdata['weather'].encode('utf-8'))
if args['nocolortemp']: # don't color temp.
output += " {0}".format(outdata['temp'])
else: # colored temperature.
output += " {0}".format(self._temp(outdata['temp']))
# windchill/heatindex are conditional on season but test with startswith to see what to include
if not outdata['windchill'].startswith("NA"):
if self.registryValue('disableColoredTemp'):
if not outdata['windchill'].startswith("NA"): # windchill.
if args['nocolortemp']: # don't color windchill.
output += " | {0} {1}".format(self._bold('Wind Chill:'), outdata['windchill'])
else:
else: # color wind chill.
output += " | {0} {1}".format(self._bold('Wind Chill:'), self._temp(outdata['windchill']))
if not outdata['heatindex'].startswith("NA"):
if self.registryValue('disableColoredTemp'):
if not outdata['heatindex'].startswith("NA"): # heatindex.
if args['nocolortemp']: # don't color heatindex.
output += " | {0} {1}".format(self._bold('Heat Index:'), outdata['heatindex'])
else:
else: # color heat index.
output += " | {0} {1}".format(self._bold('Heat Index:'), self._temp(outdata['heatindex']))
# now get into the args dict for what to include (extras)
for (k,v) in args.items():
if k in ['wind','visibility','uv','pressure','dewpoint']: # if key is in extras
if v: # if that key's value is True
output += " | {0}: {1}".format(self._bold(k.title()), outdata[k])
# add in the first forecast item in conditions + updated time.
output += " | {0}: {1} {2}: {3}".format(self._bold(forecastdata[0]['day']),\
forecastdata[0]['text'],self._bold(forecastdata[1]['day']),forecastdata[1]['text'])
for (k, v) in args.items():
if k in ['wind', 'visibility', 'uv', 'pressure', 'dewpoint']: # if key is in extras
if v: # if that key's value is True, we add it.
output += " | {0}: {1}".format(self._bold(k.title()), outdata[k].encode('utf-8'))
# add in the first two forecast item in conditions + updated time.
output += " | {0}: {1}".format(self._bold(forecastdata[0]['day'].encode('utf-8')), forecastdata[0]['text'].encode('utf-8'))
output += " {0}: {1}".format(self._bold(forecastdata[1]['day'].encode('utf-8')), forecastdata[1]['text'].encode('utf-8'))
# show Updated?
if args['updated']:
output += " | {0} {1}".format(self._bold('Updated:'), outdata['observation'])
# output.
if self.registryValue('disableANSI', msg.args[0]):
irc.reply(self._strip(output))
else:
output += " | {0} {1}".format(self._bold('Updated:'), outdata['observation'].encode('utf-8'))
# finally, output the basic weather.
irc.reply(output)
# next, for outputting, handle the extras like alerts, almanac, etc.
if args['alerts']:
output = "{0} :: {1}".format(self._bu("Alerts:"),outdata['alerts'])
if self.registryValue('disableANSI', msg.args[0]):
irc.reply(self._strip(output))
else:
irc.reply(output)
# handle almanac
# next, for outputting, handle the extras like alerts, almanac, astronomy, forecast.
if args['alerts']: # if --alerts issued.
irc.reply("{0} :: {1}".format(self._bu("Alerts:"), outdata['alerts'].encode('utf-8')))
# handle almanac if --almanac is given.
if args['almanac']:
if self.registryValue('disableColoredTemp'):
if args['nocolortemp']: # disable colored temp?
output = "{0} :: Normal High: {1} (Record: {2} in {3}) | Normal Low: {4} (Record: {5} in {6})".format(\
self._bu('Almanac:'),outdata['highnormal'],outdata['highrecord'],\
outdata['highyear'],outdata['lownormal'],outdata['lowrecord'],outdata['lowyear'])
else:
self._bu('Almanac:'), outdata['highnormal'], outdata['highrecord'], outdata['highyear'],\
outdata['lownormal'], outdata['lowrecord'], outdata['lowyear'])
else: # colored temp.
output = "{0} :: Normal High: {1} (Record: {2} in {3}) | Normal Low: {4} (Record: {5} in {6})".format(\
self._bu('Almanac:'),self._temp(outdata['highnormal']),self._temp(outdata['highrecord']),\
outdata['highyear'],self._temp(outdata['lownormal']),self._temp(outdata['lowrecord']),outdata['lowyear'])
if self.registryValue('disableANSI', msg.args[0]):
irc.reply(self._strip(output))
else:
self._bu('Almanac:'), self._temp(outdata['highnormal']), self._temp(outdata['highrecord']),\
outdata['highyear'], self._temp(outdata['lownormal']), self._temp(outdata['lowrecord']), outdata['lowyear'])
# now output to irc.
irc.reply(output)
# handle astronomy
# handle astronomy if --astronomy is given.
if args['astronomy']:
output = "{0} Moon illum: {1}% Moon age: {2}d Sunrise: {3} Sunset: {4} Length of Day: {5}".format(self._bu('Astronomy:'),outdata['moonilluminated'],\
outdata['moonage'],outdata['sunrise'],outdata['sunset'], outdata['lengthofday'])
if self.registryValue('disableANSI', msg.args[0]):
irc.reply(self._strip(output))
else:
output = "{0} Moon illum: {1}% Moon age: {2}d Sunrise: {3} Sunset: {4} Length of Day: {5}".format(\
self._bu('Astronomy:'), outdata['moonilluminated'], outdata['moonage'],outdata['sunrise'],\
outdata['sunset'], outdata['lengthofday'])
# irc output now.
irc.reply(output)
# handle main forecast if --forecast is given.
if args['forecast']:
outforecast = [] # prep string for output.
for (k,v) in fullforecastdata.items(): # iterate through forecast data.
if self.registryValue('disableColoredTemp'):
outforecast.append("{0}: {1} ({2}/{3})".format(self._bold(v['day']),v['text'],\
v['high'],v['low']))
else:
outforecast.append("{0}: {1} ({2}/{3})".format(self._bold(v['day']),v['text'],\
self._temp(v['high']),self._temp(v['low'])))
output = "{0} :: {1}".format(self._bu('Forecast:'), " | ".join(outforecast)) # string to output
if self.registryValue('disableANSI', msg.args[0]):
irc.reply(self._strip(output))
for (k, v) in fullforecastdata.items(): # iterate through forecast data.
if args['nocolortemp']:
outforecast.append("{0}: {1} ({2}/{3})".format(self._bold(v['day'].encode('utf-8')),\
v['text'].encode('utf-8'), v['high'], v['low']))
else:
outforecast.append("{0}: {1} ({2}/{3})".format(self._bold(v['day'].encode('utf-8')),\
v['text'].encode('utf-8'), self._temp(v['high']), self._temp(v['low'])))
# construct our string to output.
output = "{0} :: {1}".format(self._bu('Forecast:'), " | ".join(outforecast))
# now output to irc.
irc.reply(output)
wunderground = wrap(wunderground, [getopts({'alerts':'',
'almanac':'',
'astronomy':'',
@ -601,7 +613,9 @@ class Weather(callbacks.Plugin):
'uv':'',
'visibility':'',
'dewpoint':'',
'metric':''}), optional('text')])
'metric':'',
'nocolortemp':'',
'help':''}), optional('text')])
Class = Weather