Last active
August 29, 2015 14:10
-
-
Save bgreenlee/6b121c1b358535398b1b to your computer and use it in GitHub Desktop.
Bare-bones slash command handler for Slack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
/** | |
* Bare-bones slash command handler for Slack | |
* | |
* Commands: | |
* /weather [city] - Return the current weather for the given city | |
*/ | |
import ( | |
"bytes" | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"net/url" | |
"strings" | |
"time" | |
"github.com/bradfitz/latlong" | |
) | |
/** | |
* weatherHandler reports the weather for a given location using OpenWeatherMap | |
*/ | |
func weatherHandler(resp http.ResponseWriter, req *http.Request) { | |
// Slack sends the text after the command in the "text" query param | |
location := req.FormValue("text") | |
// make our weather API call | |
params := url.Values{} | |
params.Add("q", location) | |
params.Add("units", "imperial") // suck it, rest of the world | |
res, err := http.Get("http://api.openweathermap.org/data/2.5/weather?" + params.Encode()) | |
// read the response and parse the JSON | |
decoder := json.NewDecoder(res.Body) | |
var weatherData struct { | |
Coord *struct { | |
Lon float64 | |
Lat float64 | |
} | |
Sys *struct { | |
SunriseEpoch int64 `json:"sunrise"` | |
SunsetEpoch int64 `json:"sunset"` | |
} | |
Weather []*struct { | |
Description string | |
Icon string | |
} | |
Main *struct { | |
Temp float64 | |
TempHigh float64 `json:"temp_max"` | |
TempLow float64 `json:"temp_min"` | |
} | |
Location string `json:"name"` | |
Message string // only returned in the case of an error | |
Code interface{} `json:"cod"` // this can be either an int or a string, ugh | |
} | |
err = decoder.Decode(&weatherData) | |
if err != nil { | |
log.Println(err) | |
fmt.Fprintf(resp, "Oops, got an error: %s", err) | |
return | |
} | |
if weatherData.Main == nil { | |
if weatherData.Code.(string) == "404" { | |
fmt.Fprintf(resp, "Sorry, I couldn't find anything for '%s'", location) | |
} else { | |
fmt.Fprintf(resp, "Oops, got an error: %s", weatherData.Message) | |
} | |
return | |
} | |
// build the conditions string | |
conditions := []string{} | |
conditionsStr := "" | |
for _, conditionData := range weatherData.Weather { | |
conditions = append(conditions, conditionData.Description) | |
} | |
if len(conditions) > 2 { | |
conditionsStr = strings.Join(conditions[:len(conditions)-1], ", ") + ", and " + conditions[len(conditions)-1] | |
} else { | |
conditionsStr = strings.Join(conditions, " and ") | |
} | |
// get sunrise/sunset in the local time zone | |
timezone := latlong.LookupZoneName(weatherData.Coord.Lat, weatherData.Coord.Lon) | |
tzLocation, _ := time.LoadLocation(timezone) | |
sunrise := time.Unix(weatherData.Sys.SunriseEpoch, 0).In(tzLocation) | |
sunset := time.Unix(weatherData.Sys.SunsetEpoch, 0).In(tzLocation) | |
// send our response | |
responseStr := fmt.Sprintf("Currently %d and %s. The high today is %d and the low is %d.\nSunrise is at %s and sunset is at %s", | |
int(weatherData.Main.Temp), conditionsStr, | |
int(weatherData.Main.TempHigh), int(weatherData.Main.TempLow), | |
sunrise.Format("3:04am"), sunset.Format("3:04pm")) | |
// send the response back to Slack | |
// if you instead want a private response, replace this with: | |
// fmt.Fprint(resp, responseStr) | |
postResponse(responseStr, weatherData.Location, weatherData.Weather[0].Icon) | |
} | |
/** | |
* postResponse uses an Incoming Webhook (https://hackarts.slack.com/services/new/incoming-webhook) | |
* to post a response back to Slack. This allows us to have a public response, rather | |
* than the private one we would get if we just output the response directly to the | |
* http.ResponseWriter | |
*/ | |
func postResponse(responseText string, location string, icon string) { | |
// replace this with the url Slack gives you when you create an Incoming Webhook | |
webhookUrl := "https://hooks.slack.com/services/..." | |
type SlackResponse struct { | |
Text string `json:"text"` | |
Username string `json:"username"` | |
Icon string `json:"icon_url"` | |
} | |
response := SlackResponse{ | |
Text: responseText, | |
Username: "Weather for " + location, | |
Icon: "http://openweathermap.org/img/w/" + icon + ".png", | |
} | |
responseJSON, err := json.Marshal(response) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
_, err = http.Post(webhookUrl, "application/json", bytes.NewReader(responseJSON)) | |
if err != nil { | |
log.Println(err) | |
} | |
} | |
func main() { | |
http.HandleFunc("/weather", weatherHandler) | |
if err := http.ListenAndServe(":8080", nil); err != nil { | |
log.Fatal(err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment