danielecook
9/3/2018 - 12:58 AM

strava bitbar plugin

strava bitbar plugin

package main

/*
# <bitbar.title>Strava</bitbar.title>
# <bitbar.version>v0.0.1</bitbar.version>
# <bitbar.author>Daniel Cook</bitbar.author>
# <bitbar.author.github>danielecook</bitbar.author.github>
# <bitbar.desc>Bitbar Strava tracker</bitbar.desc>
*/

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"time"

	keychain "github.com/keybase/go-keychain"
	"github.com/tidwall/gjson"
)

const service = "Strava-API-BitBar"
const account = "access_token"
const access_group = "strava.api.bitbar"

var client = &http.Client{Timeout: 10 * time.Second}

type athlete struct {
	Bar string
}

func main() {

	args := os.Args

	/*
	   Set Access Token
	*/
	if len(args) > 1 && args[1] == "set_api_key" {
		fmt.Printf("---\n")
		cmd := exec.Command("osascript", "-e", "display dialog \"Enter Strava-API access_token\" default answer \" \" buttons {\"ok\"} with title \"Enter Strava access_token\" \n set a to the text returned of the result\n")
		out, err := cmd.CombinedOutput()
		store_api_key(out)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("%s\n", out)
		os.Exit(0)
	}

	var access_token = fetch_api_key()
	if access_token == "" {
		fmt.Println("Set Strava Access Token")
		fmt.Println("---")
		access_token_line()
		os.Exit(0)
	}

	// Fetch urls
	var athlete_url = fmt.Sprintf("https://www.strava.com/api/v3/athlete?access_token=%s", access_token)
	athlete := getUrl(athlete_url)
	user_id := gjson.Get(athlete, "id").String()
	url := fmt.Sprintf("https://www.strava.com/api/v3/athletes/%s/stats?access_token=%s", user_id, access_token)
	stats := getUrl(url)
	fmt.Printf("🏃‍♂️ %.0f ", meters_to_miles(gjson.Get(stats, "all_run_totals.distance").Float()))
	fmt.Printf("🚲 %.0f\n", meters_to_miles(gjson.Get(stats, "all_ride_totals.distance").Float()))
	fmt.Println("---")

	fmt.Printf("%s | href=https://www.strava.com/athletes/%s\n", gjson.Get(athlete, "username").String(), user_id)

	/* This Year */
	fmt.Println("Year to date")
	fmt.Printf("🏃‍♂️ %.0f miles (%v runs) | href=https://www.strava.com/athletes/%v/training/log?sport=Run \n",
		meters_to_miles(gjson.Get(stats, "ytd_run_totals.distance").Float()),
		gjson.Get(stats, "ytd_run_totals.count").Int(),
		user_id)
	fmt.Printf("🚲 %.0f miles (%v rides) | href=https://www.strava.com/athletes/%v/training/log?sport=Ride \n",
		meters_to_miles(gjson.Get(stats, "ytd_ride_totals.distance").Float()),
		gjson.Get(stats, "ytd_ride_totals.count").Int(),
		user_id)

	/* Last Four Weeks */
	fmt.Println("4 weeks")
	fmt.Printf("🏃‍♂️ %.0f miles (%v runs) | href=https://www.strava.com/athletes/%v/training/log?sport=Run \n",
		meters_to_miles(gjson.Get(stats, "recent_run_totals.distance").Float()),
		gjson.Get(stats, "recent_run_totals.count").Int(),
		user_id)
	fmt.Printf("🚲 %.0f miles (%v rides) | href=https://www.strava.com/athletes/%v/training/log?sport=Ride \n",
		meters_to_miles(gjson.Get(stats, "recent_ride_totals.distance").Float()),
		gjson.Get(stats, "recent_ride_totals.count").Int(),
		user_id)

	/* Access Token */
	fmt.Println("---")
	access_token_line()
	os.Exit(0)

}

func getUrl(url string) string {
	resp, err := client.Get(url)
	if err != nil {
		fmt.Println("API Error")
		fmt.Println("---")
		access_token_line()
		os.Exit(0)
	}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("API Error")
		fmt.Println("---")
		access_token_line()
		os.Exit(0)
	}
	return string(b)
}

func store_api_key(api_key []byte) {
	/*
	   Stores the API key
	*/
	item := keychain.NewItem()
	item.SetSecClass(keychain.SecClassGenericPassword)
	item.SetService(service)
	item.SetAccount(account)
	item.SetLabel("Strava API Key for BitBar")
	item.SetAccessGroup(access_group)
	item.SetData(api_key)
	item.SetSynchronizable(keychain.SynchronizableNo)
	item.SetAccessible(keychain.AccessibleWhenUnlocked)
	err := keychain.AddItem(item)
	if err == keychain.ErrorDuplicateItem {
		// If duplicate -- overwrite
		delete_api_key()
		keychain.AddItem(item)
	} else {
		log.Fatal(err)
	}
}

func delete_api_key() {
	/*
	   Deletes the record for the API key
	*/
	item := keychain.NewItem()
	item.SetSecClass(keychain.SecClassGenericPassword)
	item.SetService(service)
	item.SetAccount(account)
	keychain.DeleteItem(item)
}

func fetch_api_key() string {
	/*
	   Fetches the user API key from the keychain
	*/
	var password string
	query := keychain.NewItem()
	query.SetSecClass(keychain.SecClassGenericPassword)
	query.SetService(service)
	query.SetAccount(account)
	query.SetAccessGroup(access_group)
	query.SetMatchLimit(keychain.MatchLimitOne)
	query.SetReturnData(true)
	results, err := keychain.QueryItem(query)
	if err != nil {
		// Error
	} else if len(results) != 1 {
		// Not found
	} else {
		password = string(results[0].Data)
	}
	return password
}

func access_token_line() {
	args := os.Args
	fmt.Println("Set Access Token | bash=" + args[0] + " param1=set_api_key terminal=false refresh=true")
}

func meters_to_miles(meters float64) float64 {
	return meters * 0.0006213712
}