mirror of
https://github.com/jlu5/SupyPlugins.git
synced 2025-04-26 04:51:08 -05:00
Merge remote-tracking branch 'weather/devel'
This commit is contained in:
commit
54350230d3
@ -1,6 +1,8 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- pypy
|
||||
- pypy3
|
||||
|
@ -1,6 +1,7 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 spline
|
||||
Copyright (c) 2014-2015 James Lu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -2,56 +2,33 @@
|
||||
|
||||
# Limnoria plugin for Weather Underground (GLolol's fork)
|
||||
|
||||
## Introduction
|
||||
## Installation
|
||||
|
||||
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 deprecated at some point.
|
||||
You will need a working Limnoria bot on Python 2.7/3.2+ for this to work.
|
||||
|
||||
## Install
|
||||
|
||||
You will need a working Limnoria bot on Python 2.7/3.4 for this to work.
|
||||
|
||||
Go into your Limnoria plugin dir, usually `~/supybot/plugins` and run:
|
||||
1) Go into your Limnoria plugin dir, usually `~/supybot/plugins` and run:
|
||||
|
||||
```
|
||||
git clone https://github.com/GLolol/Supybot-Weather
|
||||
```
|
||||
|
||||
To install additional requirements, run:
|
||||
Alternatively, you can fetch this plugin (albeit a slightly older version) via Limnoria's PluginDownloader using: `install GLolol Weather`.
|
||||
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
or if you don't have or don't want to use root,
|
||||
|
||||
```
|
||||
pip install -r requirements.txt --user
|
||||
```
|
||||
|
||||
|
||||
Next, load the plugin:
|
||||
2) Load the plugin:
|
||||
|
||||
```
|
||||
/msg bot load Weather
|
||||
```
|
||||
|
||||
[Fetch an API key for Wunderground](http://www.wunderground.com/weather/api/) by signing up (free).
|
||||
3) [Fetch an API key from Wunderground](http://www.wunderground.com/weather/api/) by signing up (free).
|
||||
Once getting this key, you will need to set it on your bot before things will work.
|
||||
Reload once you perform this operation to start using it.
|
||||
|
||||
|
||||
```
|
||||
/msg bot config plugins.Weather.apiKey <APIKEY>
|
||||
/msg <yourbot> config plugins.Weather.apiKey <APIKEY>
|
||||
```
|
||||
|
||||
Now, reload the bot and you should be good to go:
|
||||
|
||||
```
|
||||
/msg bot reload Weather
|
||||
```
|
||||
|
||||
*Optional:* There are some config variables that can be set for the bot. They mainly control output stuff.
|
||||
4) *Optional:* There are some config variables that can be set for the bot. They mainly control output stuff.
|
||||
|
||||
```
|
||||
/msg bot config search Weather
|
||||
@ -59,9 +36,12 @@ Now, reload the bot and you should be good to go:
|
||||
|
||||
## Example Usage
|
||||
|
||||
When calling the `wunderground` command, you can use zip codes (10002), cities (New York, NY), etc. Weather Underground is pretty
|
||||
intelligent here.
|
||||
|
||||
```
|
||||
<spline> @wunderground 10002
|
||||
<myybot> New York, NY :: Rain :: 52F | Visibility: 4.0mi | Saturday: Rain. High around 55F. ...
|
||||
<myybot> New York, NY :: Rain :: 52F | Visibility: 4.0mi | Saturday: Rain. High around 55F...
|
||||
```
|
||||
|
||||
## Features
|
||||
@ -72,14 +52,8 @@ There are a ton of options to configure. You can find these via:
|
||||
/msg bot config search Weather
|
||||
```
|
||||
|
||||
Many of these are also available via --help when calling the wunderground command.
|
||||
|
||||
Users can also have their location remembered by the plugin's internal database so that
|
||||
they will not have to continually type in their location. NOTE: It uses their nick only,
|
||||
so if they are on a different nick, even with an identical hostmask, it will not match.
|
||||
|
||||
You can use zipcodes (10002), cities (New York, NY), etc. Weather Underground is pretty
|
||||
intelligent here.
|
||||
they will not have to continually type in their location.
|
||||
|
||||
```
|
||||
<spline> @setweather 10002
|
||||
@ -93,7 +67,7 @@ This now allows a user to type in the weather command w/o any arguments:
|
||||
<myybot> Manchester, NH :: Rain :: 45F | Visibility: 10.0mi | Saturday: Occasional light rain. High 56F. ...
|
||||
```
|
||||
|
||||
Users can also have the bot remember their preferred options, such as using Metric when displaying weather:
|
||||
Users can also have the bot remember their preferred options, such as using metric units when displaying weather:
|
||||
|
||||
```
|
||||
<spline> @setuser metric False
|
||||
|
@ -17,21 +17,27 @@ def configure(advanced):
|
||||
from supybot.questions import expect, anything, something, yn
|
||||
conf.registerPlugin('Weather', True)
|
||||
|
||||
|
||||
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,'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?""")))
|
||||
conf.registerGlobalValue(Weather,'almanac', registry.Boolean(False, ("""Display almanac by default?""")))
|
||||
conf.registerGlobalValue(Weather,'astronomy', registry.Boolean(False, ("""Display astronomy by default?""")))
|
||||
conf.registerGlobalValue(Weather,'showPressure', registry.Boolean(False, ("""Show pressure in output?""")))
|
||||
conf.registerGlobalValue(Weather,'showWind', registry.Boolean(False, ("""Show wind in output?""")))
|
||||
conf.registerGlobalValue(Weather,'showUpdated', registry.Boolean(False, ("""Show updated in output?""")))
|
||||
conf.registerChannelValue(Weather,'showImperialAndMetric', registry.Boolean(True, ("""In channel, display output with Imperial and Metric?""")))
|
||||
conf.registerGlobalValue(Weather,'lang', registry.String('EN', ("""language to use. See docs for available codes.""")))
|
||||
conf.registerGlobalValue(Weather, 'apiKey',
|
||||
registry.String('', ("""Sets the API key for the plugin. You can obtain an API key at http://www.wunderground.com/weather/api/."""), private=True))
|
||||
conf.registerChannelValue(Weather, 'useImperial',
|
||||
registry.Boolean(True, ("""Determines whether imperial units (Fahrenheit, etc.) will be used.""")))
|
||||
conf.registerGlobalValue(Weather,'forecast',
|
||||
registry.Boolean(True, ("""Determines whether forecasts will be displayed by default.""")))
|
||||
conf.registerGlobalValue(Weather,'alerts',
|
||||
registry.Boolean(False, ("""Determines whether forecasts will be displayed by default.""")))
|
||||
conf.registerGlobalValue(Weather, 'almanac',
|
||||
registry.Boolean(False, ("""Determines whether almanac will be displayed by default.""")))
|
||||
conf.registerGlobalValue(Weather, 'astronomy',
|
||||
registry.Boolean(False, ("""Determines whether astronomy will be displayed by default.""")))
|
||||
conf.registerGlobalValue(Weather, 'showPressure',
|
||||
registry.Boolean(False, ("""Determines whether pressure will be displayed by default.""")))
|
||||
conf.registerGlobalValue(Weather, 'showWind',
|
||||
registry.Boolean(False, ("""Determines whether winde will be displayed by default.""")))
|
||||
conf.registerGlobalValue(Weather, 'showUpdated',
|
||||
registry.Boolean(False, ("""Determines whether the bot will show the data's "last updated" time by default.""")))
|
||||
conf.registerGlobalValue(Weather, 'lang',
|
||||
registry.String('EN', ("""Determines the language used by the plugin.""")))
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=250:
|
||||
|
@ -1,21 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
###
|
||||
# Copyright (c) 2012-2014, spline
|
||||
# Copyright (c) 2014-2015, James Lu
|
||||
# All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
# this software and associated documentation files (the "Software"), to deal in
|
||||
# the Software without restriction, including without limitation the rights to
|
||||
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
# the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
###
|
||||
# my libs
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import json # json.
|
||||
from math import floor # for wind.
|
||||
import sqlite3 # userdb.
|
||||
import json
|
||||
from math import floor
|
||||
import sqlite3
|
||||
try:
|
||||
from itertools import izip
|
||||
except ImportError: # python3
|
||||
except ImportError: # Python 3
|
||||
izip = zip
|
||||
# extra supybot libs
|
||||
import supybot.conf as conf
|
||||
import supybot.log as log
|
||||
# supybot libs
|
||||
import supybot.utils as utils
|
||||
from supybot.commands import *
|
||||
import supybot.plugins as plugins
|
||||
@ -43,14 +60,13 @@ class WeatherDB():
|
||||
def makeDb(self):
|
||||
"""Create our DB."""
|
||||
|
||||
self.log.info("WeatherDB: Checking/Creating DB.")
|
||||
self.log.info("Weather: 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,
|
||||
alerts INTEGER DEFAULT 0,
|
||||
almanac INTEGER DEFAULT 0,
|
||||
astronomy INTEGER DEFAULT 0,
|
||||
@ -67,7 +83,7 @@ class WeatherDB():
|
||||
cursor = conn.cursor() # the old table is 4.
|
||||
tablelength = len([l[1] for l in cursor.execute("pragma table_info('users')").fetchall()])
|
||||
if tablelength == 4: # old table is 4: users, location, metric, colortemp.
|
||||
self.log.info("Table length is 4. We need to upgrade.")
|
||||
self.log.info("Weather: Upgrading database version.")
|
||||
columns = ['alerts', 'almanac', 'astronomy', 'forecast', 'pressure', 'wind', 'uv', 'visibility', 'dewpoint', 'humidity', 'updated']
|
||||
for column in columns:
|
||||
try:
|
||||
@ -129,19 +145,14 @@ class WeatherDB():
|
||||
|
||||
|
||||
class Weather(callbacks.Plugin):
|
||||
"""Add the help for "@plugin help Weather" here
|
||||
This should describe *how* to use this plugin."""
|
||||
"""This plugin provides access to information from Weather Underground."""
|
||||
threaded = True
|
||||
|
||||
def __init__(self, irc):
|
||||
self.__parent = super(Weather, self)
|
||||
self.__parent.__init__(irc)
|
||||
self.APIKEY = self.registryValue('apiKey')
|
||||
self.db = WeatherDB()
|
||||
|
||||
def die(self):
|
||||
self.__parent.die()
|
||||
|
||||
##############
|
||||
# FORMATTING #
|
||||
##############
|
||||
@ -152,9 +163,6 @@ class Weather(callbacks.Plugin):
|
||||
def _bu(self, string):
|
||||
return ircutils.underline(ircutils.bold(string))
|
||||
|
||||
def _strip(self, string):
|
||||
return ircutils.stripFormatting(string)
|
||||
|
||||
############################
|
||||
# INTERNAL WEATHER HELPERS #
|
||||
############################
|
||||
@ -162,105 +170,64 @@ class Weather(callbacks.Plugin):
|
||||
def _weatherSymbol(self, code):
|
||||
"""Return a unicode symbol based on weather status."""
|
||||
|
||||
table = {'partlycloudy':'~☁',
|
||||
'cloudy':'☁',
|
||||
'tstorms':'⚡',
|
||||
'sunny':'☀',
|
||||
'snow':'❄',
|
||||
'sleet':'☄',
|
||||
'rain':'☔',
|
||||
'mostlysunny':'~☀',
|
||||
'mostlycloudy':'~☁',
|
||||
'hazy':'♒',
|
||||
'fog':'♒',
|
||||
'flurries':'❄',
|
||||
'clear':'☼',
|
||||
'chanceflurries':'?❄',
|
||||
'chancerain':'?☔',
|
||||
'chancesleet':'?❄',
|
||||
'chancesnow':'?❄',
|
||||
'chancetstorms':'?☔' }
|
||||
table = {'partlycloudy': '~☁',
|
||||
'cloudy': '☁',
|
||||
'tstorms': '⚡',
|
||||
'sunny': '☀',
|
||||
'snow': '❄',
|
||||
'sleet': '☄',
|
||||
'rain': '☔',
|
||||
'mostlysunny': '~☀',
|
||||
'mostlycloudy': '~☁',
|
||||
'hazy': '♒',
|
||||
'fog': '♒',
|
||||
'flurries': '❄',
|
||||
'clear': '☼',
|
||||
'chanceflurries': '?❄',
|
||||
'chancerain': '?☔',
|
||||
'chancesleet': '?❄',
|
||||
'chancesnow': '?❄',
|
||||
'chancetstorms': '?☔'}
|
||||
# return symbol from table.
|
||||
try:
|
||||
return table[code]
|
||||
except KeyError:
|
||||
return "unknown"
|
||||
|
||||
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:
|
||||
symbol = "[ C ] (decreasing moon)"
|
||||
elif phase < 0.30:
|
||||
symbol = "[ C ] (half moon)"
|
||||
elif phase < 0.45:
|
||||
symbol = "[ ( ] (decreasing moon)"
|
||||
elif phase < 0.65:
|
||||
symbol = "[ ] (new moon)"
|
||||
elif phase < 0.80:
|
||||
symbol = "[ ) ] (waxing moon)"
|
||||
elif phase < 0.80:
|
||||
symbol = "[ D ] (half moon)"
|
||||
else:
|
||||
symbol = "[ D ] (waxing moon)"
|
||||
# return.
|
||||
return symbol
|
||||
|
||||
def _temp(self, x):
|
||||
def _temp(self, f, c=None):
|
||||
"""Returns a colored string based on the temperature."""
|
||||
|
||||
# lets be safe and wrap in a try/except because we can't always trust data purity.
|
||||
try:
|
||||
if x.startswith('NA'): # Wunderground sends a field that's not available
|
||||
return x
|
||||
# first, convert into F so we only have one table.
|
||||
if x.endswith('C'): # c.
|
||||
x = float(x[:-1]) * 9 / 5 + 32 # remove C + math into float(F).
|
||||
unit = "C"
|
||||
else: # f.
|
||||
x = float(x[:-1]) # remove F. str->float.
|
||||
unit = "F"
|
||||
if str(f).startswith('NA'): # Wunderground sends a field that's not available
|
||||
return f
|
||||
f = int(f)
|
||||
if not c:
|
||||
c = int((f - 32) * 5/9)
|
||||
# determine color.
|
||||
if x < 10.0:
|
||||
if f < 10.0:
|
||||
color = 'light blue'
|
||||
elif 10.0 <= x <= 32.0:
|
||||
elif 10.0 <= f <= 32.0:
|
||||
color = 'teal'
|
||||
elif 32.1 <= x <= 50.0:
|
||||
elif 32.1 <= f <= 50.0:
|
||||
color = 'blue'
|
||||
elif 50.1 <= x <= 60.0:
|
||||
elif 50.1 <= f <= 60.0:
|
||||
color = 'light green'
|
||||
elif 60.1 <= x <= 70.0:
|
||||
elif 60.1 <= f <= 70.0:
|
||||
color = 'green'
|
||||
elif 70.1 <= x <= 80.0:
|
||||
elif 70.1 <= f <= 80.0:
|
||||
color = 'yellow'
|
||||
elif 80.1 <= x <= 90.0:
|
||||
elif 80.1 <= f <= 90.0:
|
||||
color = 'orange'
|
||||
elif x > 90.0:
|
||||
elif f > 90.0:
|
||||
color = 'red'
|
||||
else:
|
||||
color = 'light grey'
|
||||
# return.
|
||||
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)
|
||||
except Exception as e: # rutroh. something went wrong.
|
||||
self.log.info("_temp: ERROR trying to convert temp: {0} message: {1}".format(x, e))
|
||||
return x
|
||||
|
||||
def _tw(self, bol, x):
|
||||
"""This is a convenience handle that wraps _temp."""
|
||||
|
||||
# make sure we have 'bol', which should come in from args['nocolortemp'].
|
||||
# since the option is a negation, we assume NO.
|
||||
if not bol: # COLOR IT.
|
||||
x = self._temp(x)
|
||||
return x
|
||||
else:
|
||||
return x
|
||||
return ircutils.mircColor(("{0}F/{1}C".format(f, c)), color)
|
||||
except ValueError as e:
|
||||
self.log.info("Weather: ValueError trying to convert temp: {0} message: {1}".format(f, e))
|
||||
return f
|
||||
|
||||
def _wind(self, angle, useSymbols=False):
|
||||
"""Converts degrees to direction for wind. Can optionally return a symbol."""
|
||||
@ -285,8 +252,8 @@ class Weather(callbacks.Plugin):
|
||||
"""<setting> <True|False>
|
||||
|
||||
Sets a user's <setting> to True or False.
|
||||
Settings: alerts, almanac, astronomy, forecast, pressure, wind, uv, visibility, dewpoint, humidity, updated
|
||||
Ex: metric True or colortemp False
|
||||
Valid settings include: alerts, almanac, astronomy, forecast, pressure,
|
||||
wind, uv, visibility, dewpoint, humidity, and updated.
|
||||
"""
|
||||
|
||||
# first, lower
|
||||
@ -294,19 +261,18 @@ class Weather(callbacks.Plugin):
|
||||
# grab a list of valid settings.
|
||||
validset = self.db.getsettings()
|
||||
if optset not in validset:
|
||||
irc.error("'{0}' is an invalid setting. Must be one of: {1}".format(optset, " | ".join(sorted([i for i in validset]))), Raise=True)
|
||||
return
|
||||
# setting value True/False
|
||||
irc.error(format("%r is an invalid setting. Must be one of: %L.", optset,
|
||||
sorted(validset)), Raise=True)
|
||||
if optbool: # True.
|
||||
value = 1
|
||||
else: # False.
|
||||
value = 0
|
||||
# check user first.
|
||||
if not self.db.getuser(msg.nick.lower()): # user exists
|
||||
irc.error("You're not in the database. You must setweather first.", Raise=True)
|
||||
irc.error("You are not in the database; you must use 'setweather' first.", Raise=True)
|
||||
else: # user is valid. perform the op.
|
||||
self.db.setsetting(msg.nick.lower(), optset, value)
|
||||
irc.reply("I have changed {0}'s {1} setting to {2}".format(msg.nick, optset, value))
|
||||
irc.replySuccess()
|
||||
|
||||
setuser = wrap(setuser, [('somethingWithoutSpaces'), ('boolean')])
|
||||
|
||||
@ -317,10 +283,8 @@ class Weather(callbacks.Plugin):
|
||||
Use your zip/postal code to keep it simple.
|
||||
Ex: setweather 10012
|
||||
"""
|
||||
|
||||
# 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))
|
||||
irc.replySuccess()
|
||||
|
||||
setweather = wrap(setweather, [('text')])
|
||||
|
||||
@ -332,217 +296,135 @@ class Weather(callbacks.Plugin):
|
||||
"""Internal helper to find a location via Wunderground's autocomplete API."""
|
||||
|
||||
url = 'http://autocomplete.wunderground.com/aq?query=%s' % utils.web.urlquote(q)
|
||||
#self.log.info("WUAC URL: {0}".format(url))
|
||||
# try and fetch.
|
||||
self.log.debug("Autocomplete URL: %s", url)
|
||||
try:
|
||||
page = utils.web.getUrl(url)
|
||||
except Exception as e: # something didn't work.
|
||||
self.log.info("_wuac: ERROR: Trying to open {0} message: {1}".format(url, e))
|
||||
except Exception as e:
|
||||
self.log.info("Weather: (_wuac) Error trying to open {0} message: {1}".format(url, e))
|
||||
return None
|
||||
# now process json and return.
|
||||
try:
|
||||
data = json.loads(page.decode('utf-8'))
|
||||
loc = data['RESULTS'][0]['zmw'] # find the first zmw.
|
||||
loc = "zmw:%s" % loc # return w/zmw: attached.
|
||||
# ZMW is in some ways a lot like Wunderground's location ID Codes, for when locations
|
||||
# are too ambiguous. (e.g. looking up "France", which is a country with many different
|
||||
# locations!)
|
||||
for item in data['RESULTS']:
|
||||
# Sometimes the autocomplete will lead us to more disambiguation pages...
|
||||
# which cause lots of errors in processing!
|
||||
if item['tz'] != 'MISSING':
|
||||
loc = "zmw:%s" % item['zmw']
|
||||
break
|
||||
return loc
|
||||
except Exception as e:
|
||||
self.log.info("_wuac: ERROR processing json in {0} :: {1}".format(url, e))
|
||||
self.log.info("Weather: (_wuac) Error processing JSON in {0} :: {1}".format(url, e))
|
||||
return None
|
||||
|
||||
def _wunderjson(self, url, location):
|
||||
"""Fetch wunderground JSON and return."""
|
||||
|
||||
# first, construct the url properly.
|
||||
if url.endswith('/'): # cheap way to strip the tailing /
|
||||
if url.endswith('/'): # cheap way to strip the trailing /
|
||||
url = '%sq/%s.json' % (url, utils.web.urlquote(location))
|
||||
else:
|
||||
url = '%s/q/%s.json' % (url, utils.web.urlquote(location))
|
||||
# now actually fetch the url.
|
||||
try:
|
||||
self.log.info("URL: {0}".format(url))
|
||||
self.log.debug("Weather URL: {0}".format(url))
|
||||
page = utils.web.getUrl(url)
|
||||
return page
|
||||
except Exception as e: # something didn't work.
|
||||
self.log.info("_wunderjson: ERROR Trying to open {0} message: {1}".format(url, e))
|
||||
self.log.info("Weather: (_wunderjson) Error trying to open {0} message: {1}".format(url, e))
|
||||
return None
|
||||
|
||||
####################
|
||||
# PUBLIC FUNCTIONS #
|
||||
####################
|
||||
|
||||
def wunderground(self, irc, msg, args, optlist, optinput):
|
||||
def wunderground(self, irc, msg, args, optinput):
|
||||
"""[--options] <location>
|
||||
|
||||
Fetch weather and forcast information for <location>
|
||||
Fetches weather and forecast information for <location>.
|
||||
|
||||
Location must be one of: US state/city (CA/San_Francisco), zipcode, country/city (Australia/Sydney), airport code (KJFK)
|
||||
Use --help to list all options.
|
||||
Location must be one of: US state/city (CA/San_Francisco), zip code, country/city (Australia/Sydney), or an airport code (KJFK).
|
||||
Ex: 10021 or Sydney, Australia or KJFK
|
||||
"""
|
||||
|
||||
# first, check if we have an API key. Useless w/o this.
|
||||
if len(self.APIKEY) < 1 or not self.APIKEY or self.APIKEY == "Not set":
|
||||
irc.error("Need a Wunderground API key. Set config plugins.Weather.apiKey and reload Weather.", Raise=True)
|
||||
apikey = self.registryValue('apiKey')
|
||||
if not apikey:
|
||||
irc.error("No Wunderground API key was defined. Set 'config plugins.Weather.apiKey' and reload the plugin.",
|
||||
Raise=True)
|
||||
|
||||
# urlargs will be used to build the url to query the API.
|
||||
# besides lang, these are unmutable values that should not be changed.
|
||||
urlArgs = {'features':['conditions', 'forecast'],
|
||||
'lang':self.registryValue('lang'),
|
||||
'bestfct':'1',
|
||||
'pws':'0' }
|
||||
# now, figure out the rest of the options for fetching and displaying weather.
|
||||
# some of these are for the query and the others are for output.
|
||||
# the order will always go global->channel (supybot config) -> user.
|
||||
urlArgs = {'features': ['conditions', 'forecast'],
|
||||
'lang': self.registryValue('lang'),
|
||||
'bestfct': '1',
|
||||
'pws': '0' }
|
||||
loc = None
|
||||
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'),
|
||||
'pressure':self.registryValue('showPressure'),
|
||||
'wind':self.registryValue('showWind'),
|
||||
'updated':self.registryValue('showUpdated'),
|
||||
'showImperialAndMetric':self.registryValue('showImperialAndMetric', msg.args[0]),
|
||||
'forecast':False,
|
||||
'humidity':False,
|
||||
'strip':False,
|
||||
'uv':False,
|
||||
'visibility':False,
|
||||
'dewpoint':False }
|
||||
args = {'imperial': self.registryValue('useImperial', msg.args[0]),
|
||||
'alerts': self.registryValue('alerts'),
|
||||
'almanac': self.registryValue('almanac'),
|
||||
'astronomy': self.registryValue('astronomy'),
|
||||
'pressure': self.registryValue('showPressure'),
|
||||
'wind': self.registryValue('showWind'),
|
||||
'updated': self.registryValue('showUpdated'),
|
||||
'forecast': False,
|
||||
'humidity': False,
|
||||
'uv': False,
|
||||
'visibility': False,
|
||||
'dewpoint': False}
|
||||
|
||||
# instead of doing optlist, we need to handle the location/options to set initially.
|
||||
# first, check if there is a user so we can grab their settings.
|
||||
usersetting = self.db.getweather(msg.nick.lower()) # check the db.
|
||||
if usersetting: # user is found. lets grab their location and settings.
|
||||
for (k, v) in usersetting.items(): # iterate over settings dict returned from getweather row.
|
||||
# set specific settings based on keys that won't 1:1 match.
|
||||
if k == 'location': # location. look down below this for how the logic is handled.
|
||||
loc = v # copy over their location from the DB to loc.
|
||||
elif k == 'metric': # metric
|
||||
if v == 1: # true.
|
||||
args['imperial'] = False
|
||||
else: # 0 = false.
|
||||
args['imperial'] = True
|
||||
elif k == 'colortemp': # colortemp.
|
||||
if v == 1: # true.
|
||||
args['nocolortemp'] = False
|
||||
else: # false. the 'nocolortemp' values are inverse.
|
||||
args['nocolortemp'] = True
|
||||
else: # rest of them are 1:1.
|
||||
if v == 1: # if value is 1, or true.
|
||||
args[k] = True
|
||||
else: # argument is 0 or False.
|
||||
args[k] = False
|
||||
else: # user was not found.
|
||||
usersetting = self.db.getweather(msg.nick.lower())
|
||||
if usersetting:
|
||||
for (k, v) in usersetting.items():
|
||||
args[k] = v
|
||||
loc = usersetting["location"]
|
||||
args['imperial'] = (not usersetting["metric"])
|
||||
else:
|
||||
if not optinput: # location was also not specified, so we must bail.
|
||||
irc.error("I did not find a preset location for you. Set via setweather <location>", Raise=True)
|
||||
irc.error("I did not find a preset location for you. Set one via 'setweather <location>'.", Raise=True)
|
||||
|
||||
# handle optlist (getopts). this will manipulate output via args dict.
|
||||
# we must do this after the dblookup for users as it would always override.
|
||||
if optlist:
|
||||
for (key, value) in optlist:
|
||||
if key == "metric":
|
||||
args['imperial'] = False
|
||||
if key == 'alerts':
|
||||
args['alerts'] = True
|
||||
if key == 'forecast':
|
||||
args['forecast'] = True
|
||||
if key == 'almanac':
|
||||
args['almanac'] = True
|
||||
if key == 'pressure':
|
||||
args['pressure'] = True
|
||||
if key == 'humidity':
|
||||
args['humidity'] = True
|
||||
if key == 'wind':
|
||||
args['wind'] = True
|
||||
if key == 'uv':
|
||||
args['uv'] = True
|
||||
if key == 'visibility':
|
||||
args['visibility'] = True
|
||||
if key == 'dewpoint':
|
||||
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 that we're done with 'input things'
|
||||
# we need to decide on how to handle the location.
|
||||
# optinput = user specified location, regardless if they're known or not.
|
||||
# loc = the location that can come back if a user is known and this is set.
|
||||
# both of these might not be valid locations. however, if a user specifies a location, we should look it up.
|
||||
if optinput: # if we have optinput, regardless if the user is known or not, autocomplete it.
|
||||
if optinput:
|
||||
wloc = self._wuac(optinput)
|
||||
if not wloc: # error looking up the location.
|
||||
if not wloc:
|
||||
irc.error("I could not find a valid location for: {0}".format(optinput), Raise=True)
|
||||
elif loc and not optinput: # user is known. location is set. no optinput.
|
||||
wloc = loc # set wloc as their location. worst case, the user gets an error for setting it wrong.
|
||||
elif loc: # user is known. location is set. no optinput.
|
||||
wloc = loc
|
||||
else: # no optinput. no location. error out. this should happen above but lets be redundant.
|
||||
irc.error("You must specify a city to search for weather.", Raise=True)
|
||||
|
||||
# build url now. first, apikey. then, iterate over urlArgs and insert.
|
||||
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.
|
||||
url = 'http://api.wunderground.com/api/%s/' % (apikey)
|
||||
for check in ['alerts', 'almanac', 'astronomy']:
|
||||
if args[check]: # if args['value'] is True, either via config or getopts.
|
||||
if args[check]:
|
||||
urlArgs['features'].append(check) # append to dict->key (list)
|
||||
# now, we use urlArgs dict to append to url.
|
||||
for (key, value) in urlArgs.items():
|
||||
if key == "features": # will always be at least conditions.
|
||||
url += "".join([item + '/' for item in value]) # listcmp the features/
|
||||
if key == "lang" or key == "bestfct" or key == "pws": # rest added with key:value
|
||||
if key in ("lang", "bestfct", "pws"): # rest added with key:value
|
||||
url += "{0}:{1}/".format(key, value)
|
||||
|
||||
# now that we're done, lets finally make our API call.
|
||||
page = self._wunderjson(url, wloc)
|
||||
if not page:
|
||||
irc.error("Failed to load Wunderground API. Check the logs for more information.", Raise=True)
|
||||
|
||||
# process json.
|
||||
try:
|
||||
data = json.loads(page.decode('utf-8'))
|
||||
except Exception as e:
|
||||
self.log.error("ERROR: could not process JSON from: {0} :: {1}".format(url, e))
|
||||
self.log.error("Weather: Error processing JSON from: {0} :: {1}".format(url, e))
|
||||
irc.error("Could not process JSON from Weather Underground. Check the logs.", Raise=True)
|
||||
|
||||
# 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.error("I got an error searching '{0}'. ({1}: {2})".format(loc, errortype, errordesc), Raise=True)
|
||||
# if there is more than one city matching (Ambiguous Results). we now go with the first (best?) match.
|
||||
# this should no longer be the case with our autocomplete routine above but we'll keep this anyways.
|
||||
if 'results' in data['response']: # we grab the first location's "ZMW" which then gets constructed as location.
|
||||
first = 'zmw:%s' % data['response']['results'][0]['zmw'] # grab the "first" location and create the
|
||||
# grab this first location and search again.
|
||||
page = self._wunderjson(url, first)
|
||||
if not page:
|
||||
irc.error("Failed to load Wunderground API.", Raise=True)
|
||||
# we're here if we got the second search (best?) now lets reload the json and continue.
|
||||
data = json.loads(page.decode('utf-8'))
|
||||
outdata = {'weather': data['current_observation']['weather'],
|
||||
'location': data['current_observation']['display_location']['full'],
|
||||
'humidity': data['current_observation']['relative_humidity'],
|
||||
'uv': data['current_observation']['UV']}
|
||||
|
||||
# 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']
|
||||
outdata['humidity'] = data['current_observation']['relative_humidity']
|
||||
outdata['uv'] = data['current_observation']['UV']
|
||||
|
||||
# handle wind. check if there is none first.
|
||||
if data['current_observation']['wind_mph'] < 1: # no wind.
|
||||
outdata['wind'] = "None"
|
||||
else: # we do have wind. process differently.
|
||||
if args['imperial']: # imperial units for wind.
|
||||
else:
|
||||
if args['imperial']:
|
||||
outdata['wind'] = "{0}@{1}mph".format(self._wind(data['current_observation']['wind_degrees']), data['current_observation']['wind_mph'])
|
||||
if int(data['current_observation']['wind_gust_mph']) > 0: # gusts?
|
||||
if int(data['current_observation']['wind_gust_mph']) > 0:
|
||||
outdata['wind'] += " ({0}mph gusts)".format(data['current_observation']['wind_gust_mph'])
|
||||
else: # handle metric units for wind.
|
||||
else:
|
||||
outdata['wind'] = "{0}@{1}kph".format(self._wind(data['current_observation']['wind_degrees']),data['current_observation']['wind_kph'])
|
||||
if int(data['current_observation']['wind_gust_kph']) > 0: # gusts?
|
||||
if int(data['current_observation']['wind_gust_kph']) > 0:
|
||||
outdata['wind'] += " ({0}kph gusts)".format(data['current_observation']['wind_gust_kph'])
|
||||
|
||||
# handle the time. concept/method from WunderWeather plugin.
|
||||
@ -565,118 +447,55 @@ class Weather(callbacks.Plugin):
|
||||
outdata['observation'] = '1hr ago'
|
||||
else:
|
||||
outdata['observation'] = '{0}hrs ago'.format(s/3600)
|
||||
|
||||
# handle basics like temp/pressure/dewpoint. big conditional here
|
||||
# as we can display Imperial + Metric, or one or the other.
|
||||
if args['showImperialAndMetric']:
|
||||
# lets put C and F into strings to make it easier.
|
||||
tf = str(data['current_observation']['temp_f']) + 'F'
|
||||
tc = str(data['current_observation']['temp_c']) + 'C'
|
||||
outdata['temp'] = "{0}/{1}".format(self._tw(args['nocolortemp'], tf), self._tw(args['nocolortemp'], tc))
|
||||
# now lets do pressure.
|
||||
pin = str(data['current_observation']['pressure_in']) + 'in'
|
||||
pmb = str(data['current_observation']['pressure_mb']) + 'mb'
|
||||
outdata['pressure'] = "{0}/{1}".format(pin, pmb)
|
||||
# dewpoint.
|
||||
dpf = str(data['current_observation']['dewpoint_f']) + 'F'
|
||||
dpc = str(data['current_observation']['dewpoint_c']) + 'C'
|
||||
outdata['dewpoint'] = "{0}/{1}".format(self._tw(args['nocolortemp'], dpf), self._tw(args['nocolortemp'], dpc))
|
||||
# heatindex.
|
||||
hif = str(data['current_observation']['heat_index_f']) + 'F'
|
||||
hic = str(data['current_observation']['heat_index_c']) + 'C'
|
||||
outdata['heatindex'] = "{0}/{1}".format(self._tw(args['nocolortemp'], hif), self._tw(args['nocolortemp'], hic))
|
||||
# windchill.
|
||||
wcf = str(data['current_observation']['windchill_f']) + 'F'
|
||||
wcc = str(data['current_observation']['windchill_c']) + 'C'
|
||||
outdata['windchill'] = "{0}/{1}".format(self._tw(args['nocolortemp'], wcf), self._tw(args['nocolortemp'], wcc))
|
||||
# feels like
|
||||
flf = str(data['current_observation']['feelslike_f']) + 'F'
|
||||
flc = str(data['current_observation']['feelslike_c']) + 'C'
|
||||
outdata['feelslike'] = "{0}/{1}".format(self._tw(args['nocolortemp'], flf), self._tw(args['nocolortemp'], flc))
|
||||
# visibility.
|
||||
vmi = str(data['current_observation']['visibility_mi']) + 'mi'
|
||||
vkm = str(data['current_observation']['visibility_km']) + 'km'
|
||||
outdata['visibility'] = "{0}/{1}".format(vmi, vkm)
|
||||
else: # don't display both (default)
|
||||
if args['imperial']: # assigns the symbol based on metric.
|
||||
outdata['temp'] = self._tw(args['nocolortemp'], str(data['current_observation']['temp_f']) + 'F')
|
||||
outdata['pressure'] = str(data['current_observation']['pressure_in']) + 'in'
|
||||
outdata['dewpoint'] = self._tw(args['nocolortemp'], str(data['current_observation']['dewpoint_f']) + 'F')
|
||||
outdata['heatindex'] = self._tw(args['nocolortemp'], str(data['current_observation']['heat_index_f']) + 'F')
|
||||
outdata['windchill'] = self._tw(args['nocolortemp'], str(data['current_observation']['windchill_f']) + 'F')
|
||||
outdata['feelslike'] = self._tw(args['nocolortemp'], str(data['current_observation']['feelslike_f']) + 'F')
|
||||
outdata['visibility'] = str(data['current_observation']['visibility_mi']) + 'mi'
|
||||
else: # metric.
|
||||
outdata['temp'] = self._tw(args['nocolortemp'], str(data['current_observation']['temp_c']) + 'C')
|
||||
outdata['pressure'] = str(data['current_observation']['pressure_mb']) + 'mb'
|
||||
outdata['dewpoint'] = self._tw(args['nocolortemp'], str(data['current_observation']['dewpoint_c']) + 'C')
|
||||
outdata['heatindex'] = self._tw(args['nocolortemp'], str(data['current_observation']['heat_index_c']) + 'C')
|
||||
outdata['windchill'] = self._tw(args['nocolortemp'], str(data['current_observation']['windchill_c']) + 'C')
|
||||
outdata['feelslike'] = self._tw(args['nocolortemp'], str(data['current_observation']['feelslike_c']) + 'C')
|
||||
outdata['visibility'] = str(data['current_observation']['visibility_km']) + 'km'
|
||||
outdata['temp'] = self._temp(data['current_observation']['temp_f'])
|
||||
# pressure.
|
||||
pin = str(data['current_observation']['pressure_in']) + 'in'
|
||||
pmb = str(data['current_observation']['pressure_mb']) + 'mb'
|
||||
outdata['pressure'] = "{0}/{1}".format(pin, pmb)
|
||||
# dewpoint.
|
||||
outdata['dewpoint'] = self._temp(data['current_observation']['dewpoint_f'])
|
||||
# heatindex.
|
||||
outdata['heatindex'] = self._temp(data['current_observation']['heat_index_f'])
|
||||
# windchill.
|
||||
outdata['windchill'] = self._temp(data['current_observation']['windchill_f'])
|
||||
# feels like
|
||||
outdata['feelslike'] = self._temp(data['current_observation']['feelslike_f'])
|
||||
# visibility.
|
||||
vmi = str(data['current_observation']['visibility_mi']) + 'mi'
|
||||
vkm = str(data['current_observation']['visibility_km']) + 'km'
|
||||
outdata['visibility'] = "{0}/{1}".format(vmi, vkm)
|
||||
|
||||
# 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']: # imperial.
|
||||
tmpdict['text'] = forecastday['fcttext']
|
||||
else: # metric.
|
||||
tmpdict['text'] = forecastday['fcttext_metric']
|
||||
forecastdata[int(forecastday['period'])] = tmpdict
|
||||
if args['imperial']:
|
||||
text = forecastday['fcttext']
|
||||
else:
|
||||
text = forecastday['fcttext_metric']
|
||||
forecastdata[int(forecastday['period'])] = {'day': forecastday['title'],
|
||||
'text': text}
|
||||
|
||||
# now this is the --forecast part.
|
||||
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']: # imperial.
|
||||
tmpdict['high'] = forecastday['high']['fahrenheit'] + "F"
|
||||
tmpdict['low'] = forecastday['low']['fahrenheit'] + "F"
|
||||
else: # metric.
|
||||
tmpdict['high'] = forecastday['high']['celsius'] + "C"
|
||||
tmpdict['low'] = forecastday['low']['celsius'] + "C"
|
||||
fullforecastdata[int(forecastday['period'])] = tmpdict
|
||||
|
||||
# handle almanac
|
||||
if args['almanac']:
|
||||
outdata['highyear'] = data['almanac']['temp_high'].get('recordyear', 'NA')
|
||||
outdata['lowyear'] = data['almanac']['temp_low'].get('recordyear', 'NA')
|
||||
if args['imperial']: # imperial.
|
||||
outdata['highnormal'] = data['almanac']['temp_high']['normal']['F'] + "F"
|
||||
outdata['lownormal'] = data['almanac']['temp_low']['normal']['F'] + "F"
|
||||
if outdata['highyear'] != "NA" and outdata['lowyear'] != "NA":
|
||||
outdata['highrecord'] = data['almanac']['temp_high']['record']['F']
|
||||
outdata['lowrecord'] = data['almanac']['temp_low']['record']['F']
|
||||
else:
|
||||
outdata['highrecord'] = "NA"
|
||||
outdata['lowrecord'] = "NA"
|
||||
else: # metric.
|
||||
outdata['highnormal'] = data['almanac']['temp_high']['normal']['C'] + "C"
|
||||
outdata['lownormal'] = data['almanac']['temp_low']['normal']['C'] + "C"
|
||||
if outdata['highyear'] != "NA" and outdata['lowyear'] != "NA":
|
||||
outdata['highrecord'] = data['almanac']['temp_high']['record']['C']
|
||||
outdata['lowrecord'] = data['almanac']['temp_low']['record']['C']
|
||||
else:
|
||||
outdata['highrecord'] = "NA"
|
||||
outdata['lowrecord'] = "NA"
|
||||
|
||||
# handle astronomy
|
||||
if args['astronomy']:
|
||||
outdata['moonilluminated'] = data['moon_phase']['percentIlluminated']
|
||||
outdata['moonage'] = data['moon_phase']['ageOfMoon']
|
||||
sunriseh = data['moon_phase']['sunrise']['hour']
|
||||
sunrisem = data['moon_phase']['sunrise']['minute']
|
||||
sunseth = data['moon_phase']['sunset']['hour']
|
||||
sunsetm = data['moon_phase']['sunset']['minute']
|
||||
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((((int(sunseth)-int(sunriseh))+float((int(sunsetm)-int(sunrisem))/60.0))*60),60)
|
||||
output = "{0} :: {1} ::".format(self._bold(outdata['location']), outdata['weather'])
|
||||
output += " {0} ".format(outdata['temp'])
|
||||
# humidity.
|
||||
if args['humidity']:
|
||||
output += " (Humidity: {0}) ".format(outdata['humidity'])
|
||||
# windchill/heatindex are conditional on season but test with startswith to see what to include
|
||||
if not outdata['windchill'].startswith("NA"):
|
||||
output += "| {0} {1} ".format(self._bold('Wind Chill:'), outdata['windchill'])
|
||||
if not outdata['heatindex'].startswith("NA"):
|
||||
output += "| {0} {1} ".format(self._bold('Heat Index:'), outdata['heatindex'])
|
||||
# now get into the args dict for what to include (extras)
|
||||
for k in ('wind', 'visibility', 'uv', 'pressure', 'dewpoint'):
|
||||
if args[k]:
|
||||
output += "| {0}: {1} ".format(self._bold(k.title()), outdata[k])
|
||||
# add in the first two forecast item in conditions + updated time.
|
||||
output += "| {0}: {1}".format(self._bold(forecastdata[0]['day']), forecastdata[0]['text'])
|
||||
output += " {0}: {1}".format(self._bold(forecastdata[1]['day']), forecastdata[1]['text'])
|
||||
if args['updated']:
|
||||
output += " | {0} {1}".format(self._bold('Updated:'), outdata['observation'])
|
||||
# finally, output the basic weather.
|
||||
irc.reply(output)
|
||||
|
||||
# handle alerts
|
||||
if args['alerts']: # only look for alerts if there.
|
||||
@ -686,86 +505,65 @@ class Weather(callbacks.Plugin):
|
||||
outdata['alerts'] = utils.str.normalizeWhitespace(outdata['alerts']) # fix pesky double whitespacing.
|
||||
else: # no alerts found (empty).
|
||||
outdata['alerts'] = "No alerts."
|
||||
irc.reply("{0} {1}".format(self._bu("Alerts:"), outdata['alerts']))
|
||||
|
||||
# OUTPUT.
|
||||
# we go step-by-step to build the proper string. ° u" \u00B0C"
|
||||
output = "{0} :: {1} ::".format(self._bold(outdata['location']), outdata['weather'])
|
||||
# add in temperature.
|
||||
output += " {0}".format(outdata['temp'])
|
||||
# humidity.
|
||||
if args['humidity']: # display humidity?
|
||||
output += " (Humidity: {0}) ".format(outdata['humidity'])
|
||||
else:
|
||||
output += " "
|
||||
# windchill/heatindex are conditional on season but test with startswith to see what to include
|
||||
if not outdata['windchill'].startswith("NA"): # windchill.
|
||||
output += "| {0} {1} ".format(self._bold('Wind Chill:'), outdata['windchill'])
|
||||
if not outdata['heatindex'].startswith("NA"): # heatindex.
|
||||
output += "| {0} {1} ".format(self._bold('Heat Index:'), 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, we add it.
|
||||
output += "| {0}: {1} ".format(self._bold(k.title()), outdata[k])
|
||||
# add in the first two forecast item in conditions + updated time.
|
||||
output += "| {0}: {1}".format(self._bold(forecastdata[0]['day']), forecastdata[0]['text'])
|
||||
output += " {0}: {1}".format(self._bold(forecastdata[1]['day']), forecastdata[1]['text'])
|
||||
# show Updated?
|
||||
if args['updated']:
|
||||
output += " | {0} {1}".format(self._bold('Updated:'), outdata['observation'])
|
||||
# finally, output the basic weather.
|
||||
irc.reply(output)
|
||||
|
||||
# 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']))
|
||||
# handle almanac if --almanac is given.
|
||||
# handle almanac
|
||||
if args['almanac']:
|
||||
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: # 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'])
|
||||
# now output to irc.
|
||||
try:
|
||||
outdata['highyear'] = data['almanac']['temp_high'].get('recordyear')
|
||||
outdata['lowyear'] = data['almanac']['temp_low'].get('recordyear')
|
||||
outdata['highaverage'] = self._temp(data['almanac']['temp_high']['average']['F'])
|
||||
outdata['lowaverage'] = self._temp(data['almanac']['temp_low']['average']['F'])
|
||||
if outdata['highyear'] != "NA" and outdata['lowyear'] != "NA":
|
||||
outdata['highrecord'] = self._temp(data['almanac']['temp_high']['record']['F'])
|
||||
outdata['lowrecord'] = self._temp(data['almanac']['temp_low']['record']['F'])
|
||||
else:
|
||||
outdata['highrecord'] = outdata['lowrecord'] = "NA"
|
||||
except KeyError:
|
||||
output = "%s Not available." % self._bu('Almanac:')
|
||||
else:
|
||||
output = ("{0} Average High: {1} (Record: {2} in {3}) | Average Low: {4} (Record: {5} in {6})".format(
|
||||
self._bu('Almanac:'), outdata['highaverage'], outdata['highrecord'], outdata['highyear'],
|
||||
outdata['lowaverage'], outdata['lowrecord'], outdata['lowyear']))
|
||||
irc.reply(output)
|
||||
# handle astronomy if --astronomy is given.
|
||||
# handle astronomy
|
||||
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'])
|
||||
# irc output now.
|
||||
sunriseh = data['moon_phase']['sunrise']['hour']
|
||||
sunrisem = data['moon_phase']['sunrise']['minute']
|
||||
sunseth = data['moon_phase']['sunset']['hour']
|
||||
sunsetm = data['moon_phase']['sunset']['minute']
|
||||
sunrise = "{0}:{1}".format(sunriseh, sunrisem)
|
||||
sunset = "{0}:{1}".format(sunseth, sunsetm)
|
||||
# Oh god, this one-liner... -GLolol
|
||||
lengthofday = "%dh%dm" % divmod((((int(sunseth)-int(sunriseh))+float((int(sunsetm)-int(sunrisem))/60.0))*60 ),60)
|
||||
astronomy = {'Moon illum:': str(data['moon_phase']['percentIlluminated']) + "%",
|
||||
'Moon age:': str(data['moon_phase']['ageOfMoon']) + "d",
|
||||
'Sunrise:': sunrise,
|
||||
'Sunset:': sunset,
|
||||
'Length of Day:': lengthofday}
|
||||
output = [format('%s %s', self._bold(k), v) for k, v in sorted(astronomy.items())]
|
||||
output = format("%s %s", self._bu('Astronomy:'), " | ".join(output))
|
||||
irc.reply(output)
|
||||
# handle main forecast if --forecast is given.
|
||||
# handle forecast
|
||||
if args['forecast']:
|
||||
fullforecastdata = {} # key = day (int), value = dict of forecast data.
|
||||
for forecastday in data['forecast']['simpleforecast']['forecastday']:
|
||||
high = self._temp(forecastday['high']['fahrenheit'])
|
||||
low = self._temp(forecastday['low']['fahrenheit'])
|
||||
tmpdict = {'day': forecastday['date']['weekday_short'],
|
||||
'symbol': self._weatherSymbol(forecastday['icon']),
|
||||
'text': forecastday['conditions'],
|
||||
'low': low,
|
||||
'high': high}
|
||||
fullforecastdata[int(forecastday['period'])] = tmpdict
|
||||
outforecast = [] # prep string for 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']),\
|
||||
outforecast.append("{0}: {1} (High: {2} Low: {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'])))
|
||||
# construct our string to output.
|
||||
output = "{0} :: {1}".format(self._bu('Forecast:'), " | ".join(outforecast))
|
||||
# now output to irc.
|
||||
output = "{0} {1}".format(self._bu('Forecast:'), " | ".join(outforecast))
|
||||
irc.reply(output)
|
||||
|
||||
wunderground = wrap(wunderground, [getopts({'alerts':'',
|
||||
'almanac':'',
|
||||
'astronomy':'',
|
||||
'forecast':'',
|
||||
'pressure':'',
|
||||
'wind':'',
|
||||
'uv':'',
|
||||
'visibility':'',
|
||||
'dewpoint':'',
|
||||
'humidity':'',
|
||||
'metric':'',
|
||||
'nocolortemp':'',
|
||||
'help':''}), optional('text')])
|
||||
wunderground = wrap(wunderground, [optional('text')])
|
||||
|
||||
Class = Weather
|
||||
|
||||
|
@ -13,19 +13,18 @@ class WeatherTestCase(PluginTestCase):
|
||||
|
||||
def setUp(self):
|
||||
PluginTestCase.setUp(self)
|
||||
apiKey = os.environ.get('apiKey')
|
||||
apiKey = os.environ.get('weather_apikey')
|
||||
if not apiKey:
|
||||
e = """The Wunderground API key has not been set.
|
||||
please set this value correctly and try again:
|
||||
'export apiKey=<key>' for bash users"""
|
||||
please set this value correctly via the environment variable
|
||||
"weather_apikey"."""
|
||||
raise Exception(e)
|
||||
conf.supybot.plugins.Weather.apiKey.setValue(apiKey)
|
||||
|
||||
def testWeather(self):
|
||||
self.assertSnarfResponse('reload Weather', 'The operation succeeded.')
|
||||
self.assertRegexp('wunderground 10002', 'New York, NY')
|
||||
self.assertSnarfResponse('setweather 10002', "I have changed test's weather ID to 10002")
|
||||
self.assertSnarfResponse('setuser metric True', "I have changed test's metric setting to 1")
|
||||
self.assertNotError('setweather 10002')
|
||||
self.assertNotError('setuser metric True')
|
||||
self.assertRegexp('wunderground', 'New York, NY')
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
||||
|
Loading…
x
Reference in New Issue
Block a user