Skip to content
This repository has been archived by the owner on May 27, 2021. It is now read-only.

Commit

Permalink
🚀
Browse files Browse the repository at this point in the history
  • Loading branch information
kejadlen committed Nov 18, 2014
0 parents commit b89e778
Show file tree
Hide file tree
Showing 85 changed files with 369 additions and 0 deletions.
Binary file added 2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Forecast Workflow for Alfred

![screenshot][screenshot]

[screenshot]: http://i.imgur.com/enspDWu.png

# Requirements

- [Alfred](http://www.alfredapp.com/)
- [Alfred Powerpack](http://www.alfredapp.com/powerpack/)
- OS X Mavericks

# Installation

Install the workflow. Edit the script filter to add your location and API keys:

- FORECAST_API_KEY: Get an API key [here][forecast-api-key].
- GOOGLE_API_KEY: Get an API key [here][google-api-key]. Only required if
getting the forecast for an arbitrary location.
- DEFAULT_LOCATION
- DEFAULT_LAT_LONG: Required if GOOGLE_API_KEY is not used. Format: `lat,long`.

[forecast-api-key]: https://developer.forecast.io/register
[google-api-key]: https://developers.google.com/maps/documentation/geocoding/#api_key

# TODO

- Sparklines for precipitation?
- Make installation easier
- Handle errors gracefully
- Caching? (Probably unnecessary...)

# Attributions

- Icons from [Climacons](http://adamwhitcroft.com/climacons/)
44 changes: 44 additions & 0 deletions alfred.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class Items < DelegateClass(Array)
attr_reader :items

def initialize
@items = []
super(@items)
end

def to_s
ERB.new(<<-XML).result(binding)
<?xml version="1.0"?>
<items>
<%= items.map {|item| item.to_s.split("\n").map {|line| ' ' << line }}.join("\n").strip %>
</items>
XML
end
end

class Item
attr_reader *%i[ uid arg valid
title subtitle icon ]
def initialize(**kwargs)
@uid = kwargs.fetch(:uid).to_s.encode(xml: :attr)
@arg = kwargs[:arg].to_s.encode(xml: :attr)
@valid = kwargs.fetch(:valid, false) ? 'yes' : 'no'
@title = kwargs.fetch(:title).encode(xml: :text)
@subtitle = kwargs[:subtitle] && kwargs[:subtitle].encode(xml: :text)
@icon = kwargs[:icon] && kwargs[:icon].encode(xml: :text)
end

def to_s
ERB.new(<<-XML, nil, '%>').result(binding)
<item arg=<%= arg %> uid=<%= uid %> valid="<%= valid %>">
<title><%= title %></title>
% if subtitle
<subtitle><%= subtitle %></subtitle>
% end
% if icon
<icon><%= icon %></icon>
% end
</item>
XML
end
end
108 changes: 108 additions & 0 deletions forecast.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
require 'delegate'
require 'erb'
require 'yaml'

require_relative 'alfred'
require_relative 'forecaster'
require_relative 'location'

ICONS = {
'clear-day' => 'Sun',
'clear-night' => 'Moon',
'rain' => 'Cloud-Rain',
'snow' => 'Cloud-Snow',
'sleet' => 'Cloud-Snow-Alt',
'wind' => 'Wind',
'fog' => 'Cloud-Fog',
'cloudy' => 'Cloud',
'partly-cloudy-day' => 'Cloud-Sun',
'partly-cloudy-night' => 'Cloud-Moon',
}

Precipitation = Struct.new(:intensity, :probability) do
def self.from_forecast(forecast)
self.new(*forecast.values_at('precipIntensity', 'precipProbability'))
end

def human_intensity
case intensity
when 0...0.002
'no'
when 0.002...0.017
'very light'
when 0.017...0.1
'light'
when 0.1...0.4
'moderate'
else
'heavy'
end
end

def to_s
"#{(probability*100).to_i}% chance of #{human_intensity} rain."
end
end

query = ARGV.shift || ''
location = if query.empty?
lat, long = ENV.fetch('DEFAULT_LAT_LONG', '').split(?,)
Location.new(ENV['DEFAULT_LOCATION'], lat, long)
else
Location.new(query)
end
forecast = Forecaster.forecast(location)

items = Items.new

items << Item.new(
uid: :location,
arg: "#{location.lat.round(4)},#{location.long.round(4)}",
valid: true,
title: location.name,
icon: 'icons/forecast.ico',
)

currently = forecast['currently']
precip = Precipitation.from_forecast(currently)
subtitle = [ "#{currently['temperature'].round}°" ]
subtitle << "Feels like #{currently['apparentTemperature'].round}°"
subtitle << precip.to_s if precip.probability > 0
items << Item.new(
uid: :currently,
title: currently['summary'],
subtitle: subtitle.join(' · '),
icon: "icons/#{ICONS[currently['icon']]}.png",
)

minutely = forecast['minutely']
items << Item.new(
uid: :minutely,
title: minutely['summary'],
icon: "icons/#{ICONS[minutely['icon']]}.png",
)

hourly = forecast['hourly']
items << Item.new(
uid: :hourly,
title: hourly['summary'],
icon: "icons/#{ICONS[hourly['icon']]}.png",
)

forecast['daily']['data'][1..6].each do |data|
wday = Time.at(data['time']).strftime('%A')
precip = Precipitation.from_forecast(data)

subtitle = [ "Low: #{data['apparentTemperatureMin'].round}°",
"High: #{data['apparentTemperatureMax'].round}°" ]
subtitle << precip.to_s if precip.probability > 0

items << Item.new(
uid: wday,
title: "#{wday} - #{data['summary']}",
subtitle: subtitle.join(' · '),
icon: "icons/#{ICONS[data['icon']]}.png",
)
end

puts items.to_s
21 changes: 21 additions & 0 deletions forecaster.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'json'
require 'open-uri'

Forecaster = Struct.new(:api_key) do
def self.forecast(location)
forecaster.forecast(location)
end

def self.forecaster
return @forecaster if defined?(@forecaster)

@forecaster = self.new(ENV['FORECAST_API_KEY'])
end

def forecast(location)
lat, long = location.lat, location.long
url = "https://api.forecast.io/forecast/#{api_key}/#{lat},#{long}"
response = JSON.load(open(url))
end
end

28 changes: 28 additions & 0 deletions geocoder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'json'
require 'open-uri'
require 'uri'

Geocoder = Struct.new(:api_key) do
def self.geocode(location)
geocoder.geocode(location)
end

def self.geocoder
return @geocoder if defined?(@geocoder)

@geocoder = self.new(ENV['GOOGLE_API_KEY'])
end

def geocode(location)
url = 'https://maps.googleapis.com/maps/api/geocode/json'
query = URI.encode_www_form(address: location, api_key: api_key)
response = JSON.load(open("#{url}?#{query}"))
result = response['results'][0]

name = result['formatted_address']
location = result['geometry']['location']
lat, long = location.values_at('lat', 'lng')

[name, lat, long]
end
end
Binary file added icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Download.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Drizzle-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Drizzle-Moon-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Drizzle-Moon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Drizzle-Sun-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Drizzle-Sun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Drizzle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Fog-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Fog-Moon-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Fog-Moon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Fog-Sun-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Fog-Sun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Fog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Hail-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Hail-Moon-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Hail-Moon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Hail-Sun-Alt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Hail-Sun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Hail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Lightning-Moon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Lightning-Sun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Lightning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Moon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/Cloud-Rain-Alt.png
Binary file added icons/Cloud-Rain-Moon-Alt.png
Binary file added icons/Cloud-Rain-Moon.png
Binary file added icons/Cloud-Rain-Sun-Alt.png
Binary file added icons/Cloud-Rain-Sun.png
Binary file added icons/Cloud-Rain.png
Binary file added icons/Cloud-Refresh.png
Binary file added icons/Cloud-Snow-Alt.png
Binary file added icons/Cloud-Snow-Moon-Alt.png
Binary file added icons/Cloud-Snow-Moon.png
Binary file added icons/Cloud-Snow-Sun-Alt.png
Binary file added icons/Cloud-Snow-Sun.png
Binary file added icons/Cloud-Snow.png
Binary file added icons/Cloud-Sun.png
Binary file added icons/Cloud-Upload.png
Binary file added icons/Cloud-Wind-Moon.png
Binary file added icons/Cloud-Wind-Sun.png
Binary file added icons/Cloud-Wind.png
Binary file added icons/Cloud.png
Binary file added icons/Compass-East.png
Binary file added icons/Compass-North.png
Binary file added icons/Compass-South.png
Binary file added icons/Compass-West.png
Binary file added icons/Compass.png
Binary file added icons/Degrees-Celcius.png
Binary file added icons/Degrees-Fahrenheit.png
Binary file added icons/Moon-First-Quarter.png
Binary file added icons/Moon-Full.png
Binary file added icons/Moon-Last-Quarter.png
Binary file added icons/Moon-New.png
Binary file added icons/Moon-Waning-Crescent.png
Binary file added icons/Moon-Waning-Gibbous.png
Binary file added icons/Moon-Waxing-Crescent.png
Binary file added icons/Moon-Waxing-Gibbous.png
Binary file added icons/Moon.png
Binary file added icons/Shades.png
Binary file added icons/Snowflake.png
Binary file added icons/Sun-Low.png
Binary file added icons/Sun-Lower.png
Binary file added icons/Sun.png
Binary file added icons/Sunrise.png
Binary file added icons/Sunset.png
Binary file added icons/Thermometer-100.png
Binary file added icons/Thermometer-25.png
Binary file added icons/Thermometer-50.png
Binary file added icons/Thermometer-75.png
Binary file added icons/Thermometer-Zero.png
Binary file added icons/Thermometer.png
Binary file added icons/Tornado.png
Binary file added icons/Umbrella.png
Binary file added icons/Wind.png
Binary file added icons/forecast.ico
Binary file not shown.
100 changes: 100 additions & 0 deletions info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>bundleid</key>
<string>com.kejadlen.forecast</string>
<key>category</key>
<string>Internet</string>
<key>connections</key>
<dict>
<key>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</key>
<array>
<dict>
<key>destinationuid</key>
<string>5E2D96BA-31B2-4800-9A2B-B999285680A0</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
</dict>
</array>
</dict>
<key>createdby</key>
<string>Alpha Chen</string>
<key>description</key>
<string></string>
<key>disabled</key>
<false/>
<key>name</key>
<string>Forecast</string>
<key>objects</key>
<array>
<dict>
<key>config</key>
<dict>
<key>plusspaces</key>
<false/>
<key>url</key>
<string>http://forecast.io/#/f/{query}</string>
<key>utf8</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.action.openurl</string>
<key>uid</key>
<string>5E2D96BA-31B2-4800-9A2B-B999285680A0</string>
<key>version</key>
<integer>0</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>argumenttype</key>
<integer>1</integer>
<key>escaping</key>
<integer>127</integer>
<key>keyword</key>
<string>forecast</string>
<key>runningsubtext</key>
<string>Retriving location/weather...</string>
<key>script</key>
<string>export FORECAST_API_KEY=
export GOOGLE_API_KEY=
export DEFAULT_LOCATION="Seattle, WA"
ruby forecast.rb {query}</string>
<key>title</key>
<string>Forecast</string>
<key>type</key>
<integer>0</integer>
<key>withspace</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.input.scriptfilter</string>
<key>uid</key>
<string>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</string>
<key>version</key>
<integer>0</integer>
</dict>
</array>
<key>readme</key>
<string></string>
<key>uidata</key>
<dict>
<key>2A5C0A87-204E-49EA-94A7-8E62BB4EFD8A</key>
<dict>
<key>ypos</key>
<real>10</real>
</dict>
<key>5E2D96BA-31B2-4800-9A2B-B999285680A0</key>
<dict>
<key>ypos</key>
<real>10</real>
</dict>
</dict>
<key>webaddress</key>
<string>http://github.com/kejadlen/forecast.workflow</string>
</dict>
</plist>
16 changes: 16 additions & 0 deletions location.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require_relative 'geocoder'

class Location
attr_accessor :name, :lat, :long, :geocoder

def initialize(name, lat=nil, long=nil, geocoder=Geocoder)
@name, @lat, @long, @geocoder = name, lat, long, geocoder

geocode! unless lat && long
end

def geocode!
self.name, self.lat, self.long = geocoder.geocode(name)
end
end

17 changes: 17 additions & 0 deletions spark.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Spark
TICKS = %w[ ]

attr_reader :data

def initialize(*data)
@data = data
end

def to_s
min = data.min
max = data.max
range = (max - min).to_f
graph = data.map {|i| TICKS[(TICKS.size - 1) * (i - min) / range] }.join
"#{min} #{graph} #{max}"
end
end

0 comments on commit b89e778

Please sign in to comment.