diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..138cb045 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +DS_Store +gcal/client_secret.json diff --git a/bamboohr/widget.go b/bamboohr/widget.go new file mode 100644 index 00000000..917905e9 --- /dev/null +++ b/bamboohr/widget.go @@ -0,0 +1,26 @@ +package bamboohr + +import ( + "fmt" + + "github.com/rivo/tview" +) + +func Widget() tview.Primitive { + items := Fetch() + + widget := tview.NewTextView() + widget.SetBorder(true) + widget.SetDynamicColors(true) + widget.SetTitle(" 🐨 Away ") + + data := "" + for _, item := range items { + str := fmt.Sprintf(" [green]%s[white]\n %s - %s\n\n", item.Name(), item.PrettyStart(), item.PrettyEnd()) + data = data + str + } + + fmt.Fprintf(widget, "%s", data) + + return widget +} diff --git a/gcal/.DS_Store b/gcal/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/gcal/.DS_Store differ diff --git a/gcal/client.go b/gcal/client.go new file mode 100644 index 00000000..829a36c6 --- /dev/null +++ b/gcal/client.go @@ -0,0 +1,125 @@ +/* +* This butt-ugly code is direct from Google itself +* https://developers.google.com/calendar/quickstart/go + */ + +package gcal + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" + "time" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "google.golang.org/api/calendar/v3" +) + +// getClient uses a Context and Config to retrieve a Token +// then generate a Client. It returns the generated Client. +func getClient(ctx context.Context, config *oauth2.Config) *http.Client { + cacheFile, err := tokenCacheFile() + if err != nil { + log.Fatalf("Unable to get path to cached credential file. %v", err) + } + tok, err := tokenFromFile(cacheFile) + if err != nil { + tok = getTokenFromWeb(config) + saveToken(cacheFile, tok) + } + return config.Client(ctx, tok) +} + +// getTokenFromWeb uses Config to request a Token. +// It returns the retrieved Token. +func getTokenFromWeb(config *oauth2.Config) *oauth2.Token { + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + fmt.Printf("Go to the following link in your browser then type the "+ + "authorization code: \n%v\n", authURL) + + var code string + if _, err := fmt.Scan(&code); err != nil { + log.Fatalf("Unable to read authorization code %v", err) + } + + tok, err := config.Exchange(oauth2.NoContext, code) + if err != nil { + log.Fatalf("Unable to retrieve token from web %v", err) + } + return tok +} + +// tokenCacheFile generates credential file path/filename. +// It returns the generated credential path/filename. +func tokenCacheFile() (string, error) { + usr, err := user.Current() + if err != nil { + return "", err + } + tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials") + os.MkdirAll(tokenCacheDir, 0700) + return filepath.Join(tokenCacheDir, + url.QueryEscape("calendar-go-quickstart.json")), err +} + +// tokenFromFile retrieves a Token from a given file path. +// It returns the retrieved Token and any read error encountered. +func tokenFromFile(file string) (*oauth2.Token, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + t := &oauth2.Token{} + err = json.NewDecoder(f).Decode(t) + defer f.Close() + return t, err +} + +// saveToken uses a file path to create a file and store the +// token in it. +func saveToken(file string, token *oauth2.Token) { + fmt.Printf("Saving credential file to: %s\n", file) + f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Unable to cache oauth token: %v", err) + } + defer f.Close() + json.NewEncoder(f).Encode(token) +} + +func Fetch() *calendar.Events { + ctx := context.Background() + + b, err := ioutil.ReadFile("./gcal/client_secret.json") + if err != nil { + log.Fatalf("Unable to read client secret file: %v", err) + } + + config, err := google.ConfigFromJSON(b, calendar.CalendarReadonlyScope) + if err != nil { + log.Fatalf("Unable to parse client secret file to config: %v", err) + } + client := getClient(ctx, config) + + srv, err := calendar.New(client) + if err != nil { + log.Fatalf("Unable to retrieve calendar Client %v", err) + } + + t := time.Now().Format(time.RFC3339) + events, err := srv.Events.List("primary").ShowDeleted(false). + SingleEvents(true).TimeMin(t).MaxResults(10).OrderBy("startTime").Do() + if err != nil { + log.Fatalf("Unable to retrieve next ten of the user's events. %v", err) + } + + return events +} diff --git a/gcal/widget.go b/gcal/widget.go new file mode 100644 index 00000000..31dcba4b --- /dev/null +++ b/gcal/widget.go @@ -0,0 +1,35 @@ +package gcal + +import ( + "fmt" + "time" + + "github.com/rivo/tview" +) + +func Widget() tview.Primitive { + events := Fetch() + + widget := tview.NewTextView() + widget.SetBorder(true) + widget.SetDynamicColors(true) + widget.SetTitle(" 🐸 Calendar ") + + data := "" + for _, item := range events.Items { + ts, _ := time.Parse(time.RFC3339, item.Start.DateTime) + timestamp := ts.Format("Mon Jan _2 15:04:05 2006") + + color := "red" + if ts.Before(time.Now()) { + color = "grey" + } + + str := fmt.Sprintf(" [%s]%s[white]\n %s\n\n", color, item.Summary, timestamp) + data = data + str + } + + fmt.Fprintf(widget, "%s", data) + + return widget +} diff --git a/github/client.go b/github/client.go new file mode 100644 index 00000000..9d2b6b37 --- /dev/null +++ b/github/client.go @@ -0,0 +1,11 @@ +package github + +import ( + _ "github.com/google/go-github/github" +) + +func Fetch() string { + //client := github.NewClient(nil) + + return "" +} diff --git a/github/github.go b/github/github.go deleted file mode 100644 index 19ee27ee..00000000 --- a/github/github.go +++ /dev/null @@ -1,3 +0,0 @@ -package wtfgithub - -import () diff --git a/github/widget.go b/github/widget.go new file mode 100644 index 00000000..03d2ecf2 --- /dev/null +++ b/github/widget.go @@ -0,0 +1,18 @@ +package github + +import ( + "fmt" + + "github.com/rivo/tview" +) + +func Widget() tview.Primitive { + widget := tview.NewTextView() + widget.SetBorder(true) + widget.SetDynamicColors(true) + widget.SetTitle(" 🐱 Github ") + + fmt.Fprintf(widget, "%s", "This is github") + + return widget +} diff --git a/status/widget.go b/status/widget.go new file mode 100644 index 00000000..34c85d6f --- /dev/null +++ b/status/widget.go @@ -0,0 +1,18 @@ +package status + +import ( + "fmt" + + "github.com/rivo/tview" +) + +func Widget() tview.Primitive { + widget := tview.NewTextView() + widget.SetBorder(true) + widget.SetDynamicColors(true) + widget.SetTitle(" 🦊 Status ") + + fmt.Fprintf(widget, "%s", "cats and gods\ndogs and tacs") + + return widget +} diff --git a/weather/client.go b/weather/client.go new file mode 100644 index 00000000..2424aca2 --- /dev/null +++ b/weather/client.go @@ -0,0 +1,19 @@ +package weather + +import ( + "os" + + owm "github.com/briandowns/openweathermap" +) + +func Fetch() *owm.CurrentWeatherData { + w, err := owm.NewCurrent("C", "EN", os.Getenv("WTF_OWM_API_KEY")) + if err != nil { + panic(err) + } + + //w.CurrentByName("Toronto,ON") + w.CurrentByID(6173331) + + return w +} diff --git a/weather/widget.go b/weather/widget.go new file mode 100644 index 00000000..9fc821dd --- /dev/null +++ b/weather/widget.go @@ -0,0 +1,38 @@ +package weather + +import ( + "bytes" + "fmt" + "text/template" + + "github.com/rivo/tview" +) + +const weatherTemplate = ` + {{range .Weather}}{{.Description}}{{end}} + + Current: {{.Main.Temp}}° C + + High: {{.Main.TempMax}}° C + Low: {{.Main.TempMin}}° C + ` + +func Widget() tview.Primitive { + data := Fetch() + + widget := tview.NewTextView() + widget.SetBorder(true) + widget.SetDynamicColors(true) + widget.SetTitle(fmt.Sprintf(" 🌤 Weather - %s ", data.Name)) + + //fmt.Fprintf(widget, " %s", data.Name) + var tpl bytes.Buffer + tmpl, _ := template.New("weather").Parse(weatherTemplate) + if err := tmpl.Execute(&tpl, data); err != nil { + panic(err) + } + + fmt.Fprintf(widget, " %s ", tpl.String()) + + return widget +} diff --git a/wtf.go b/wtf.go index 20f5c96e..73060a1b 100644 --- a/wtf.go +++ b/wtf.go @@ -1,42 +1,27 @@ package main import ( - "fmt" - "github.com/rivo/tview" "github.com/senorprogrammer/wtf/bamboohr" + "github.com/senorprogrammer/wtf/gcal" + "github.com/senorprogrammer/wtf/status" + "github.com/senorprogrammer/wtf/weather" ) func main() { app := tview.NewApplication() grid := tview.NewGrid() - grid.SetRows(10, 40) // 10 high, 40 high - grid.SetColumns(40, 40) // 40 wide, 40 wide + grid.SetRows(10, 40, 30) // How _high_ the row is, in terminal rows + grid.SetColumns(40, 40) // How _wide_ the column is, in terminal columns grid.SetBorder(false) - grid.AddItem(bambooView(), 0, 0, 1, 1, 0, 0, false) + grid.AddItem(bamboohr.Widget(), 0, 0, 1, 1, 0, 0, false) + grid.AddItem(gcal.Widget(), 1, 0, 1, 1, 0, 0, false) + grid.AddItem(status.Widget(), 2, 0, 2, 3, 0, 0, false) + grid.AddItem(weather.Widget(), 0, 1, 1, 1, 0, 0, false) if err := app.SetRoot(grid, true).Run(); err != nil { panic(err) } } - -func bambooView() tview.Primitive { - items := bamboohr.Fetch() - - bamboo := tview.NewTextView() - bamboo.SetBorder(true) - bamboo.SetDynamicColors(true) - bamboo.SetTitle(" 🐨 Away ") - - data := "" - for _, item := range items { - str := fmt.Sprintf("[green]%s[white]\n%s - %s\n\n", item.Name(), item.PrettyStart(), item.PrettyEnd()) - data = data + str - } - - fmt.Fprintf(bamboo, "%s", data) - - return bamboo -}