a simple slack weather bot

it’s been really hot in Los Angeles recently, and i realised i was switching back to my web browser from the Slack app to find out the current temperature downstairs an awful lot before leaving the office.

i realised that a /weather command would far more efficient. say hello to slack-weather-bot.

a simple golang backend that take a request for /weather?zip=NNNNN and then posts a quick one-liner of current conditions back to you on Slack. enjoy.


package slackweatherbot
import (
owm "github.com/briandowns/openweathermap"
const (
weatherTemplate = `It's currently {{.Main.Temp}} °F ({{range .Weather}} {{.Description}} {{end}}) `
// get the current weather conditions from openweather
func getCurrent(zip int, units, lang string, ctx context.Context) *owm.CurrentWeatherData {
// create a urlfetch http client because we're in appengine and can't use net/http default
cl := urlfetch.Client(ctx)
// establish connection to openweather API
cc, err := owm.NewCurrent(units, lang, owm.WithHttpClient(cl))
if err != nil {
log.Errorf(ctx, "ERROR handler() during owm.NewCurrent: %s", err)
return nil
cc.CurrentByZip(zip, "US")
return cc
// redirect requests to / to /weather
func handler_redirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/weather", 302)
// handle requests to /weather
// currently supports parameter of ?zip=NNNNNN or no zip parameter, in which case DEFAULT_ZIP is used
func handler_weather(w http.ResponseWriter, r *http.Request) {
// create an appengine context so we can log
ctx := appengine.NewContext(r)
// check the parameters
zip := r.URL.Query().Get("zip")
switch zip {
// if no zip parameter given, get the DEFAULT_ZIP from our env vars
case "":
zip = os.Getenv("DEFAULT_ZIP")
// convert the zip string to an int because that's what openweather wants
var zipint int
zipint, err := strconv.Atoi(zip)
if err != nil {
log.Errorf(ctx, "ERROR handler_weather() zip conversion problem: %s", err)
// get the current weather data
wd := getCurrent(zipint, os.Getenv("UNITS"), os.Getenv("LANG"), ctx)
// make the template
tmpl, err := template.New("weather").Parse(weatherTemplate)
if err != nil {
log.Errorf(ctx, "ERROR handler_weather() during template.New: %s", err)
// execute the template
err = tmpl.Execute(w, wd)
if err != nil {
log.Errorf(ctx, "ERROR handler_weather() during template.Execute: %s", err)
// we're done here
// because we're in appengine, there is no main()
func init() {
http.HandleFunc("/", handler_redirect)
http.HandleFunc("/weather", handler_weather)
// EOF

slack useragent checker

are you curious if your [corporate] Slack users are logging into Slack using a Slack desktop or mobile app, or if they’re just using the Slack webpage? here’s some example code to call the slack team.accessLogs API to output which of your Slack users are not using a desktop or mobile Slack app.


package main
import (
type SlackAccessLog struct {
Status bool `json:"ok"`
Logins []SlackAccessLogEntry `json:"logins"`
PagingData SlackAccessLogPaging `json:"paging"`
type SlackAccessLogEntry struct {
UserID string `json:"user_id"`
Username string `json:"username"`
DateFirst int64 `json:"date_first"`
DateLast int64 `json:"date_last"`
Count int `json:"count"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
ISP string `json:"isp"`
Country string `json:"country"`
Region string `json:"region"`
type SlackAccessLogPaging struct {
Count int `json:"count"`
Total int `json:"total"`
Page int `json:"page"`
Pages int `json:"pages"`
// helper func to do a case-insensitive search
func caseinsensitivecontains(a, b string) bool {
return strings.Contains(strings.ToUpper(a), strings.ToUpper(b))
var (
page int = 1
pages int = 101
token string = "REDACTED"
slackurl string = "https://slack.com/api/team.accessLogs"
func main() {
var sal SlackAccessLog
// 100 pages of JSON max from the team.accessLogs Slack API
for page := 1; page < pages; page++ {
// build the url
url := fmt.Sprintf("%s?token=%s&page=%d", slackurl, token, page)
// create the request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatal("error: %s", err)
// create the http client
client := &http.Client{}
// get the response
response, err := client.Do(req)
if err != nil {
log.Fatal("error: %s", err)
defer response.Body.Close()
// decode the JSON response into our SlackAccessLog var
if err := json.NewDecoder(response.Body).Decode(&sal); err != nil {
// range through this page of the response and ignore Slack App/Android/iPhone useragents
for _, dj := range sal.Logins {
if !(caseinsensitivecontains(dj.UserAgent, "Slack_SSB") || caseinsensitivecontains(dj.UserAgent, "Android") || caseinsensitivecontains(dj.UserAgent, "iPhone")) {
tm := time.Unix(dj.DateLast, 0)
fmt.Printf("%s\t%s\t%s\n", dj.Username, tm, dj.UserAgent)

Slack Team Directory Bot

stable release of Slack Team Directory Bot.


what is Slack Team Directory Bot? you get a Slack /slash command that lets you search your Slack Team Directory quick as a flash.

below screenshot shows example response to a Slack trying to find someone in your accounting department by typing:


within slack:



  • you deploy a go backend app to Google Appengine that responds to…
  • a custom Slack /slash command that you create in Slack