Feature done: Today's Agenda

This commit is contained in:
Brian Buller 2017-02-16 09:59:33 -06:00
parent a9d880a486
commit 113b161e0f
4 changed files with 527 additions and 63 deletions

View File

@ -4,16 +4,62 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
calendar "google.golang.org/api/calendar/v3"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
calendar "google.golang.org/api/calendar/v3"
) )
type Account struct {
CC *CalClient
Service *calendar.Service
}
func GetAccount(secret, token []byte) (*Account, error) {
var err error
a := new(Account)
// Create the CalClient
a.CC = CreateClient(secret, token)
a.CC.TokenToRaw()
// Create the Calendar Service
a.Service, err = calendar.New(a.CC.client)
if err != nil {
return nil, errors.New("Unable to retrieve calendar Client: " + err.Error())
}
return a, nil
}
func (a *Account) GetTodaysEvents() []Event {
var ret []Event
return ret
}
func (a *Account) GetDefaultCalendar() *Calendar {
c, _ := state.account.Service.CalendarList.Get("primary").Do()
return GoogleCalendarToLocal(c)
}
func (a *Account) GetCalendarList() []Calendar {
// TODO: Check if we have the calendar list cached
var ret []Calendar
calList, err := state.account.Service.CalendarList.List().Do()
if err != nil {
return ret
}
for _, c := range calList.Items {
ret = append(ret, *GoogleCalendarToLocal(c))
}
return ret
}
type CalClient struct { type CalClient struct {
rawClientSecret []byte rawClientSecret []byte
rawToken []byte rawToken []byte
@ -72,7 +118,9 @@ func (c *CalClient) tokenFromWeb() error {
log.Fatalf("Unable to read authorization code %v", err) log.Fatalf("Unable to read authorization code %v", err)
} }
c.tkn, err = c.cfg.Exchange(oauth2.NoContext, code) if c.tkn, err = c.cfg.Exchange(oauth2.NoContext, code); err == nil {
c.TokenToRaw()
}
return err return err
} }
@ -82,3 +130,7 @@ func (c *CalClient) TokenToRaw() {
json.NewEncoder(&tmpToken).Encode(t) json.NewEncoder(&tmpToken).Encode(t)
c.rawToken = tmpToken.Bytes() c.rawToken = tmpToken.Bytes()
} }
func (c *CalClient) GetRawToken() []byte {
return c.rawToken
}

127
calendar_struct.go Normal file
View File

@ -0,0 +1,127 @@
package main
import (
"time"
calendar "google.golang.org/api/calendar/v3"
)
type Calendar struct {
AccessRole string
BackgroundColor string
ColorId string
DefaultReminders []*calendar.EventReminder // TODO: Local Reminder struct?
Deleted bool
Description string
Etag string
ForegroundColor string
Hidden bool
Id string
Kind string
Location string
NotificationSettings *calendar.CalendarListEntryNotificationSettings // TODO: Needed?
Primary bool
Selected bool
Summary string
SummaryOverride string
TimeZone string
ForceSendFields []string // TODO: Needed?
NullFields []string //TODO: Needed?
}
func GoogleCalendarToLocal(c *calendar.CalendarListEntry) *Calendar {
ret := Calendar{
AccessRole: c.AccessRole,
BackgroundColor: c.BackgroundColor,
ColorId: c.ColorId,
DefaultReminders: c.DefaultReminders,
Deleted: c.Deleted,
Description: c.Description,
Etag: c.Etag,
ForegroundColor: c.ForegroundColor,
Hidden: c.Hidden,
Id: c.Id,
Kind: c.Kind,
Location: c.Location,
Primary: c.Primary,
Selected: c.Selected,
Summary: c.Summary,
SummaryOverride: c.SummaryOverride,
TimeZone: c.TimeZone,
}
return &ret
}
func (c *Calendar) GetTodaysEvents() []Event {
var ret []Event
minTime := time.Date(
time.Now().Year(),
time.Now().Month(),
time.Now().Day(),
0, 0, 0, 0, time.Local,
)
maxTime := time.Date(
time.Now().Year(),
time.Now().Month(),
time.Now().Day(),
23, 59, 59, 999999999, time.Local,
)
events, err := state.account.Service.Events.List(c.Id).
ShowDeleted(false).
SingleEvents(true).
TimeMin(minTime.Format(time.RFC3339)).
TimeMax(maxTime.Format(time.RFC3339)).
OrderBy("startTime").
Do()
if err != nil {
return ret
}
for _, e := range events.Items {
ret = append(ret, *GoogleEventToLocal(e))
}
return ret
}
func (c *Calendar) GetCalendarEvents() []Event {
var ret []Event
events, err := state.account.Service.Events.List(c.Id).ShowDeleted(false).
SingleEvents(true).OrderBy("startTime").Do()
if err != nil {
return ret
}
for _, e := range events.Items {
ret = append(ret, *GoogleEventToLocal(e))
}
return ret
}
/* For Adding a private copy of an existing event
func (c *Calendar) ImportEvent(event *Event) error {
_, err := state.account.Service.Events.Import(c.Id, event).Do()
return err
}
*/
func (c *Calendar) InsertEvent(event *Event) (*Event, error) {
e, err := state.account.Service.Events.Insert(c.Id, LocalEventToGoogle(event)).Do()
return GoogleEventToLocal(e), err
}
func (c *Calendar) DeleteEvent(eId string) error {
return state.account.Service.Events.Delete(c.Id, eId).Do()
}
func (c *Calendar) GetEvent(eId string) (*Event, error) {
e, err := state.account.Service.Events.Get(c.Id, eId).Do()
return GoogleEventToLocal(e), err
}
func (c *Calendar) QuickAdd(text string) (*Event, error) {
e, err := state.account.Service.Events.QuickAdd(c.Id, text).Do()
return GoogleEventToLocal(e), err
}
func (c *Calendar) Update(eId string, event *Event) (*Event, error) {
e, err := state.account.Service.Events.Update(c.Id, eId, LocalEventToGoogle(event)).Do()
return GoogleEventToLocal(e), err
}

240
event_struct.go Normal file
View File

@ -0,0 +1,240 @@
package main
import (
"fmt"
"time"
calendar "google.golang.org/api/calendar/v3"
)
type Event struct {
//Attachments []EventAttachment // Google Drive files
Attendees []EventAttendee
ColorId string
Created string
Creator *EventAttendee
Description string
End *calendar.EventDateTime
EndTimeUnspecified bool
Etag string
//ExtendedProperties []EventExtendedProperties
GuestsCanInviteOthers bool
GuestsCanModify bool
GuestsCanSeeOtherGuests bool
HangoutLink string
HtmlLink string
ICalUID string
Id string
Kind string
Location string
Locked bool
Organizer *EventAttendee
OriginalStartTime *calendar.EventDateTime
PrivateCopy bool
Recurrence []string
RecurringEventId string
//Reminders *EventReminders
Sequence int64
//Source *EventSource
Start *calendar.EventDateTime
Status string
Summary string
Transparency string
Updated string
Visibility string
}
func (e *Event) ToCLIString() string {
return fmt.Sprintf("%s\n%s\n", e.Summary, e.GetStartTime())
}
func (e *Event) GetStartTime() string {
tm, err := time.Parse(time.RFC3339, e.Start.DateTime)
if err != nil {
return "00:00:00"
}
return tm.Local().Format("15:04:05")
}
func GoogleEventToLocal(e *calendar.Event) *Event {
return &Event{
//Attachments []EventAttachment // Google Drive files
Attendees: GoogleAttendeeSliceToLocal(e.Attendees),
ColorId: e.ColorId,
Created: e.Created,
Creator: GoogleCreatorToLocal(e.Creator),
Description: e.Description,
End: e.End,
EndTimeUnspecified: e.EndTimeUnspecified,
Etag: e.Etag,
//ExtendedProperties []EventExtendedProperties
//GuestsCanInviteOthers: *e.GuestsCanInviteOthers,
GuestsCanModify: e.GuestsCanModify,
//GuestsCanSeeOtherGuests: *e.GuestsCanSeeOtherGuests,
HangoutLink: e.HangoutLink,
HtmlLink: e.HtmlLink,
ICalUID: e.ICalUID,
Id: e.Id,
Kind: e.Kind,
Location: e.Location,
Locked: e.Locked,
Organizer: GoogleOrganizerToLocal(e.Organizer),
OriginalStartTime: e.OriginalStartTime,
PrivateCopy: e.PrivateCopy,
Recurrence: e.Recurrence,
RecurringEventId: e.RecurringEventId,
//Reminders *EventReminders
Sequence: e.Sequence,
//Source *EventSource
Start: e.Start,
Status: e.Status,
Summary: e.Summary,
Transparency: e.Transparency,
Updated: e.Updated,
Visibility: e.Visibility,
}
}
func LocalEventToGoogle(e *Event) *calendar.Event {
return &calendar.Event{
//Attachments []EventAttachment // Google Drive files
//Attendees: GoogleAttendeeSliceToLocal(e.Attendees),
ColorId: e.ColorId,
Created: e.Created,
Creator: LocalCreatorToGoogle(e.Creator),
Description: e.Description,
End: e.End,
EndTimeUnspecified: e.EndTimeUnspecified,
Etag: e.Etag,
//ExtendedProperties []EventExtendedProperties
//GuestsCanInviteOthers: *e.GuestsCanInviteOthers,
GuestsCanModify: e.GuestsCanModify,
//GuestsCanSeeOtherGuests: *e.GuestsCanSeeOtherGuests,
HangoutLink: e.HangoutLink,
HtmlLink: e.HtmlLink,
ICalUID: e.ICalUID,
Id: e.Id,
Kind: e.Kind,
Location: e.Location,
Locked: e.Locked,
Organizer: LocalOrganizerToGoogle(e.Organizer),
OriginalStartTime: e.OriginalStartTime,
PrivateCopy: e.PrivateCopy,
Recurrence: e.Recurrence,
RecurringEventId: e.RecurringEventId,
//Reminders *EventReminders
Sequence: e.Sequence,
//Source *EventSource
Start: e.Start,
Status: e.Status,
Summary: e.Summary,
Transparency: e.Transparency,
Updated: e.Updated,
Visibility: e.Visibility,
}
}
type EventAttendee struct {
AdditionalGuests int64
Comment string
DisplayName string
Email string
Id string
Optional bool
Organizer bool
Resource bool
ResponseStatus string
Self bool
EventCreator bool
EventOrganizer bool
}
func GoogleAttendeeSliceToLocal(a []*calendar.EventAttendee) []EventAttendee {
var ret []EventAttendee
for _, e := range a {
ret = append(ret, *GoogleAttendeeToLocal(e))
}
return ret
}
func LocalAttendeeSliceToGoogle(a []EventAttendee) []*calendar.EventAttendee {
var ret []*calendar.EventAttendee
for _, e := range a {
ret = append(ret, LocalAttendeeToGoogle(&e))
}
return ret
}
func GoogleAttendeeToLocal(a *calendar.EventAttendee) *EventAttendee {
return &EventAttendee{
AdditionalGuests: a.AdditionalGuests,
Comment: a.Comment,
DisplayName: a.DisplayName,
Email: a.Email,
Id: a.Id,
Optional: a.Optional,
Organizer: a.Organizer,
Resource: a.Resource,
ResponseStatus: a.ResponseStatus,
Self: a.Self,
}
}
func LocalAttendeeToGoogle(a *EventAttendee) *calendar.EventAttendee {
return &calendar.EventAttendee{
AdditionalGuests: a.AdditionalGuests,
Comment: a.Comment,
DisplayName: a.DisplayName,
Email: a.Email,
Id: a.Id,
Optional: a.Optional,
Organizer: a.Organizer,
Resource: a.Resource,
ResponseStatus: a.ResponseStatus,
Self: a.Self,
}
}
func GoogleOrganizerToLocal(a *calendar.EventOrganizer) *EventAttendee {
return &EventAttendee{
DisplayName: a.DisplayName,
Email: a.Email,
Id: a.Id,
Self: a.Self,
EventOrganizer: true,
}
}
func LocalOrganizerToGoogle(a *EventAttendee) *calendar.EventOrganizer {
return &calendar.EventOrganizer{
DisplayName: a.DisplayName,
Email: a.Email,
Id: a.Id,
Self: a.Self,
}
}
func GoogleCreatorToLocal(a *calendar.EventCreator) *EventAttendee {
return &EventAttendee{
DisplayName: a.DisplayName,
Email: a.Email,
Id: a.Id,
Self: a.Self,
EventCreator: true,
}
}
func LocalCreatorToGoogle(a *EventAttendee) *calendar.EventCreator {
return &calendar.EventCreator{
DisplayName: a.DisplayName,
Email: a.Email,
Id: a.Id,
Self: a.Self,
}
}
type ByStartTime []Event
func (a ByStartTime) Len() int { return len(a) }
func (a ByStartTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByStartTime) Less(i, j int) bool { return a[i].Start.DateTime < a[j].Start.DateTime }

165
main.go
View File

@ -5,9 +5,8 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"time" "sort"
"strings"
calendar "google.golang.org/api/calendar/v3"
"github.com/br0xen/user-config" "github.com/br0xen/user-config"
) )
@ -22,12 +21,14 @@ type AppState struct {
Version int Version int
ClientSecret []byte ClientSecret []byte
cfg *userConfig.Config cfg *userConfig.Config
account *Account
} }
var state *AppState var state *AppState
func main() { func main() {
var err error var err error
var op string
state = &AppState{Name: AppName, Version: AppVersion} state = &AppState{Name: AppName, Version: AppVersion}
state.cfg, err = userConfig.NewConfig(state.Name) state.cfg, err = userConfig.NewConfig(state.Name)
@ -35,75 +36,118 @@ func main() {
panic(err) panic(err)
} }
for i := range os.Args { if len(os.Args) > 1 {
switch { op = os.Args[1]
case os.Args[i] == "--reinit": switch os.Args[1] {
// Reset all config case "--reinit":
for _, v := range state.cfg.GetKeyList() {
state.cfg.DeleteKey(v)
}
} }
} else {
op = "today"
} }
DoVersionCheck() DoVersionCheck()
switch op {
case "--reinit":
// Reset all config
for _, v := range state.cfg.GetKeyList() {
fmt.Println("Deleting Key: " + v)
state.cfg.DeleteKey(v)
}
case "today":
// Show everything on the calendar for today
InitComm()
// TODO: Get calendars flagged as default
list := state.account.GetCalendarList()
var todayEvents []Event
if len(list) > 0 {
for i := range list {
if list[i].Deleted {
// Deleted calendar, next please
continue
}
todayEvents = append(todayEvents, list[i].GetTodaysEvents()...)
}
sort.Sort(ByStartTime(todayEvents))
for _, e := range todayEvents {
if e.GetStartTime() == "00:00:00" {
fmt.Println("[All Day ] " + e.Summary)
} else {
fmt.Println("[" + e.GetStartTime() + "] " + e.Summary)
}
}
} else {
fmt.Printf("No calendars found.\n")
}
case "defaults":
// Show Defaults
InitComm()
case "add":
// Quick event add to primary calendar
quickAddText := strings.Join(os.Args[2:], " ")
e, err := state.account.GetDefaultCalendar().QuickAdd(quickAddText)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(e.ToCLIString())
}
case "bail":
// Just initialize communications and bail
InitComm()
}
//fmt.Println("\n-====-\n")
/*
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)
}
fmt.Println("Upcoming events:")
if len(events.Items) > 0 {
for _, i := range events.Items {
var when string
// If the DateTime is an empty string the Event is an all-day Event.
// So only Date is available.
if i.Start.DateTime != "" {
when = i.Start.DateTime
} else {
when = i.Start.Date
}
fmt.Printf("%s (%s)\n", i.Summary, when)
}
} else {
fmt.Printf("No upcoming events found.\n")
}
*/
}
func InitComm() {
var err error
sec := state.cfg.GetBytes("ClientSecret") sec := state.cfg.GetBytes("ClientSecret")
tkn := state.cfg.GetBytes("Token") tkn := state.cfg.GetBytes("Token")
c := CreateClient(sec, tkn) state.account, err = GetAccount(sec, tkn)
c.TokenToRaw()
state.cfg.SetBytes("Token", c.rawToken)
srv, err := calendar.New(c.client)
if err != nil { if err != nil {
log.Fatalf("unable to retrieve calendar Client %v", err) log.Fatalf("Unable to get Account: %v", err)
} }
listSvc := srv.CalendarList.List() // Save the Raw Token
list, err := listSvc.Do() state.cfg.SetBytes("Token", state.account.CC.GetRawToken())
if err != nil {
log.Fatalf("unable to retrieve calendar list %v", err) // If the 'defaultCalendars' cfg is set to 'primary', we need to actually but the primary cal id in there
} var defCal []string
if len(list.Items) > 0 { defCal, err = state.cfg.GetArray("defaultCalendars")
for i := range list.Items { if len(defCal) == 0 || (len(defCal) == 1 && defCal[0] == "primary") {
if list.Items[i].Deleted { gCal, err := state.account.Service.CalendarList.Get("primary").Do()
// Deleted calendar, next please if err == nil {
continue state.cfg.SetArray("defaultCalendars", []string{gCal.Id})
}
fmt.Print("(" + list.Items[i].Id + ") ")
fmt.Print(list.Items[i].Summary + "; ")
fmt.Println(list.Items[i].Description)
} }
} else {
fmt.Printf("No calendars found.\n")
}
fmt.Println("\n-====-\n")
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)
}
fmt.Println("Upcoming events:")
if len(events.Items) > 0 {
for _, i := range events.Items {
var when string
// If the DateTime is an empty string the Event is an all-day Event.
// So only Date is available.
if i.Start.DateTime != "" {
when = i.Start.DateTime
} else {
when = i.Start.Date
}
fmt.Printf("%s (%s)\n", i.Summary, when)
}
} else {
fmt.Printf("No upcoming events found.\n")
} }
} }
@ -127,6 +171,7 @@ func DoVersionCheck() {
} }
} }
state.cfg.SetInt("version", 1) state.cfg.SetInt("version", 1)
state.cfg.SetArray("defaultCalendars", []string{"primary"})
} }
state.ClientSecret = state.cfg.GetBytes("ClientSecret") state.ClientSecret = state.cfg.GetBytes("ClientSecret")
// Refetch the version from the config // Refetch the version from the config