From 39a3ea630cb45b6fa523d2cb2fe7a8b2404b688f Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Mon, 14 Sep 2020 23:01:25 +0000 Subject: [PATCH] Refactor calendar code --- calendar.go | 185 ++++++++++++++++++++++++++++++ classes.go | 322 ++++++++++++++++++++++++++-------------------------- main.go | 151 +----------------------- 3 files changed, 350 insertions(+), 308 deletions(-) create mode 100644 calendar.go diff --git a/calendar.go b/calendar.go new file mode 100644 index 0000000..684d51b --- /dev/null +++ b/calendar.go @@ -0,0 +1,185 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "time" + + "golang.org/x/oauth2/google" + "google.golang.org/api/calendar/v3" +) + +var _globalCalService *calendar.Service + +func getCalService() *calendar.Service { + if _globalCalService != nil { + return _globalCalService + } + + b, err := ioutil.ReadFile("credentials.json") + if err != nil { + log.Fatal(err) + } + + // If modifying these scopes, delete your previously saved token.json. + config, err := google.ConfigFromJSON(b, calendar.CalendarScope) + if err != nil { + log.Fatal(err) + } + client, err := getClient(config) + if err != nil { + log.Fatal(err) + } + + _globalCalService, err := calendar.New(client) + if err != nil { + log.Fatal(err) + } + + return _globalCalService +} + +func getCalendarId(calName string) (string, error) { + client := getCalService() + + calendars, err := client.CalendarList.List().Do() + if err != nil { + return "", err + } + + for _, item := range calendars.Items { + if item.Summary == calName { + return item.Id, nil + } + } + + return "", fmt.Errorf("calendar '%s' not found", calName) +} + +func updateCalendar(calName string, classes []Class, days int) error { + loc, err := time.LoadLocation("US/Pacific") + if err != nil { + return err + } + + now := time.Now().In(loc) + + calId, err := getCalendarId(calName) + if err != nil { + return err + } + + for i := 0; i < days; i++ { + date := now.Add(time.Duration(i * 24) * time.Hour) + err = updateDay(calId, date, classes) + if err != nil { + return err + } + } + return nil +} + +func updateDay(calId string, date time.Time, classes []Class) error { + eventMap, err := getDayEvents(calId, date) + if err != nil { + return err + } + + for _, class := range classes { + if !class.happensOnDay(date) { + continue + } + + if !class.tagsMatch(date) { + continue + } + + ev := class.buildEvent(date) + + old := eventMap[ev.Summary] + + if old == nil { + log.Printf("add: [%s] %s", ev.Start.DateTime, ev.Summary) + _, err := getCalService().Events.Insert(calId, ev).Do() + if err != nil { + return err + } + } else { + if !eventsEqual(old, ev) { + log.Printf("upd: [%s] %s", ev.Start.DateTime, ev.Summary) + _, err := getCalService().Events.Update(calId, old.Id, ev).Do() + if err != nil { + return err + } + } + } + } + + return nil +} + +func getDayEvents(calendarId string, t time.Time) (map[string]*calendar.Event, error) { + start := time.Date( + t.Year(), + t.Month(), + t.Day(), + 0, 0, 0, 0, + t.Location()) + + // This may be wrong for DST changes, but it's the middle of the night and probably doesn't matter + end := start.Add(24 * time.Hour) + + events, err := getCalService().Events.List(calendarId). + ShowDeleted(false). + SingleEvents(true). + TimeMin(start.Format(time.RFC3339)). + TimeMax(end.Format(time.RFC3339)). + MaxResults(250). + Do() + if err != nil { + return nil, err + } + + eventMap := map[string]*calendar.Event{} + + for _, item := range events.Items { + eventMap[item.Summary] = item + } + + return eventMap, nil +} + +func eventsEqual(a *calendar.Event, b *calendar.Event) bool { + if a.Summary != b.Summary { + return false + } + + if a.Description != b.Description { + return false + } + + if a.Start.DateTime[:19] != b.Start.DateTime[:19] { + return false + } + + if a.End.DateTime[:19] != b.End.DateTime[:19] { + return false + } + + if len(a.Attendees) != len(b.Attendees) { + return false + } + + emails := map[string]bool{} + for _, attendee := range a.Attendees { + emails[attendee.Email] = true + } + for _, attendee := range b.Attendees { + if !emails[attendee.Email] { + return false + } + } + + return true +} diff --git a/classes.go b/classes.go index 1b6eb83..6afd25e 100644 --- a/classes.go +++ b/classes.go @@ -30,176 +30,178 @@ var tags = TagMap{ }, } -var classes = []Class { - Class{ - Summary: "👋 Morning Circle", - Start: "08:30", - End: "09:00", - Days: weekDaysButFriday, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - }, - Class{ - Summary: "🏫 Community Meeting", - Start: "08:30", - End: "09:00", - Days: []time.Weekday{time.Friday}, - Zoom: "https://zoom.us/j/96371462107", - }, - Class{ - Summary: "🔢 Math (Claudia & Rachel)", - Start: "09:00", - End: "09:40", - Days: weekDays, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - Students: []string{ - "adabelle.pratt@heliosns.org", - "oliver.park@heliosns.org", +var classes = map[string][]Class { + "Hummingbirds Classes": []Class { + Class{ + Summary: "👋 Morning Circle", + Start: "08:30", + End: "09:00", + Days: weekDaysButFriday, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", }, - }, - Class{ - Summary: "💭 Dreambox", - Start: "09:40", - End: "10:00", - Days: weekDays, - }, - Class{ - Summary: "🐛 Theme", - Start: "10:30", - End: "11:30", - Days: weekDaysButFriday, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - }, - Class{ - Summary: "❤️ SEL", - Start: "10:30", - End: "11:15", - Days: []time.Weekday{time.Friday}, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - }, - Class{ - Summary: "📖 Literacy (Green)", - Start: "11:30", - End: "12:00", - Days: []time.Weekday{time.Monday, time.Wednesday}, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - Students: greenStudents, - }, - Class{ - Summary: "🤸 PE (Yellow/Green)", - Start: "11:30", - End: "12:00", - Days: []time.Weekday{time.Tuesday}, - Zoom: "https://zoom.us/j/97472507748?pwd=cW5vUFhyUjNTS2toWlZGN254U1ZqZz09", - Students: greenStudents, - }, - Class{ - Summary: "💡 iLab (Yellow/Green)", - Start: "11:30", - End: "12:00", - Days: []time.Weekday{time.Thursday}, - Zoom: "https://us02web.zoom.us/j/86403635026?pwd=S2t1WkN2dnNJZlFkejdEbjRsUmNNUT09", - Students: greenStudents, - }, - Class{ - Summary: "📚 Library", - Start: "11:30", - End: "12:00", - Days: []time.Weekday{time.Friday}, - Zoom: "https://us02web.zoom.us/my/helioslibrary?pwd=cWd4RjNqNXZXNjRjM2dYQVhYeS9Xdz09", - }, - Class{ - Summary: "🇲🇽 Spanish", - Start: "12:45", - End: "13:30", - Days: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday}, - Zoom: "https://zoom.us/j/95144364579?pwd=cGdwNTZIOE42Y0syeVcwVEFhZ1JxZz09", - Students: []string{ - "oliver.park@heliosns.org", + Class{ + Summary: "🏫 Community Meeting", + Start: "08:30", + End: "09:00", + Days: []time.Weekday{time.Friday}, + Zoom: "https://zoom.us/j/96371462107", }, - }, - Class{ - Summary: "🎨 Art", - Start: "12:45", - End: "13:30", - Days: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday}, - Zoom: "https://us02web.zoom.us/j/86969790982?pwd=TEMraThVbHhMUlBHUjFZYXZVRnpFQT09", - Students: []string{ - "adabelle.pratt@heliosns.org", + Class{ + Summary: "🔢 Math (Claudia & Rachel)", + Start: "09:00", + End: "09:40", + Days: weekDays, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", + Students: []string{ + "adabelle.pratt@heliosns.org", + "oliver.park@heliosns.org", + }, }, - }, - Class{ - Summary: "🎶 Music & Movement (Blue/Green)", - Start: "12:45", - End: "13:30", - Days: []time.Weekday{time.Friday}, - Zoom: "https://zoom.us/j/96449009866?pwd=dlBHYUwxaDRReFhjVmRKZ0Vhdjdkdz09", - Students: greenStudents, - }, - Class{ - Summary: "🎨 Art (Blue/Green)", - Start: "14:00", - End: "14:45", - Days: []time.Weekday{time.Friday}, - Zoom: "https://us02web.zoom.us/j/85434303018?pwd=c3EzQVNCMmk0L0o4bVF4QW85RTZHZz09", - Students: greenStudents, - }, - Class{ - Summary: "👋 Closing Circle", - Start: "15:00", - End: "15:15", - Days: []time.Weekday{time.Friday}, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - }, - Class{ - Summary: "🍎 Rachel & Oliver", - Start: "13:45", - End: "14:00", - Days: []time.Weekday{time.Monday}, - Tags: map[string]string{ - "week": "A", + Class{ + Summary: "💭 Dreambox", + Start: "09:40", + End: "10:00", + Days: weekDays, }, - Zoom: "https://us02web.zoom.us/j/3839164321?pwd=aDNUdzN3bjlZbG93NlNqd09hU2xmUT09", - Students: []string{ - "oliver.park@heliosns.org", + Class{ + Summary: "🐛 Theme", + Start: "10:30", + End: "11:30", + Days: weekDaysButFriday, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", }, - }, - Class{ - Summary: "🍎 Claudia & Oliver", - Start: "13:45", - End: "14:00", - Days: []time.Weekday{time.Monday}, - Tags: map[string]string{ - "week": "B", + Class{ + Summary: "❤️ SEL", + Start: "10:30", + End: "11:15", + Days: []time.Weekday{time.Friday}, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", }, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - Students: []string{ - "oliver.park@heliosns.org", + Class{ + Summary: "📖 Literacy (Green)", + Start: "11:30", + End: "12:00", + Days: []time.Weekday{time.Monday, time.Wednesday}, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", + Students: greenStudents, }, - }, - Class{ - Summary: "🍎 Claudia & Adabelle", - Start: "13:45", - End: "14:00", - Days: []time.Weekday{time.Wednesday}, - Tags: map[string]string{ - "week": "A", + Class{ + Summary: "🤸 PE (Yellow/Green)", + Start: "11:30", + End: "12:00", + Days: []time.Weekday{time.Tuesday}, + Zoom: "https://zoom.us/j/97472507748?pwd=cW5vUFhyUjNTS2toWlZGN254U1ZqZz09", + Students: greenStudents, }, - Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", - Students: []string{ - "adabelle.pratt@heliosns.org", + Class{ + Summary: "💡 iLab (Yellow/Green)", + Start: "11:30", + End: "12:00", + Days: []time.Weekday{time.Thursday}, + Zoom: "https://us02web.zoom.us/j/86403635026?pwd=S2t1WkN2dnNJZlFkejdEbjRsUmNNUT09", + Students: greenStudents, }, - }, - Class{ - Summary: "🍎 Rachel & Adabelle", - Start: "13:45", - End: "14:00", - Days: []time.Weekday{time.Wednesday}, - Tags: map[string]string{ - "week": "B", + Class{ + Summary: "📚 Library", + Start: "11:30", + End: "12:00", + Days: []time.Weekday{time.Friday}, + Zoom: "https://us02web.zoom.us/my/helioslibrary?pwd=cWd4RjNqNXZXNjRjM2dYQVhYeS9Xdz09", }, - Zoom: "https://us02web.zoom.us/j/3839164321?pwd=aDNUdzN3bjlZbG93NlNqd09hU2xmUT09", - Students: []string{ - "adabelle.pratt@heliosns.org", + Class{ + Summary: "🇲🇽 Spanish", + Start: "12:45", + End: "13:30", + Days: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday}, + Zoom: "https://zoom.us/j/95144364579?pwd=cGdwNTZIOE42Y0syeVcwVEFhZ1JxZz09", + Students: []string{ + "oliver.park@heliosns.org", + }, + }, + Class{ + Summary: "🎨 Art", + Start: "12:45", + End: "13:30", + Days: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday}, + Zoom: "https://us02web.zoom.us/j/86969790982?pwd=TEMraThVbHhMUlBHUjFZYXZVRnpFQT09", + Students: []string{ + "adabelle.pratt@heliosns.org", + }, + }, + Class{ + Summary: "🎶 Music & Movement (Blue/Green)", + Start: "12:45", + End: "13:30", + Days: []time.Weekday{time.Friday}, + Zoom: "https://zoom.us/j/96449009866?pwd=dlBHYUwxaDRReFhjVmRKZ0Vhdjdkdz09", + Students: greenStudents, + }, + Class{ + Summary: "🎨 Art (Blue/Green)", + Start: "14:00", + End: "14:45", + Days: []time.Weekday{time.Friday}, + Zoom: "https://us02web.zoom.us/j/85434303018?pwd=c3EzQVNCMmk0L0o4bVF4QW85RTZHZz09", + Students: greenStudents, + }, + Class{ + Summary: "👋 Closing Circle", + Start: "15:00", + End: "15:15", + Days: []time.Weekday{time.Friday}, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", + }, + Class{ + Summary: "🍎 Rachel & Oliver", + Start: "13:45", + End: "14:00", + Days: []time.Weekday{time.Monday}, + Tags: map[string]string{ + "week": "A", + }, + Zoom: "https://us02web.zoom.us/j/3839164321?pwd=aDNUdzN3bjlZbG93NlNqd09hU2xmUT09", + Students: []string{ + "oliver.park@heliosns.org", + }, + }, + Class{ + Summary: "🍎 Claudia & Oliver", + Start: "13:45", + End: "14:00", + Days: []time.Weekday{time.Monday}, + Tags: map[string]string{ + "week": "B", + }, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", + Students: []string{ + "oliver.park@heliosns.org", + }, + }, + Class{ + Summary: "🍎 Claudia & Adabelle", + Start: "13:45", + End: "14:00", + Days: []time.Weekday{time.Wednesday}, + Tags: map[string]string{ + "week": "A", + }, + Zoom: "https://us02web.zoom.us/j/2274643506?pwd=Nm5NUXMwOVJKbEUzNE5VSkZCQzJ2UT09", + Students: []string{ + "adabelle.pratt@heliosns.org", + }, + }, + Class{ + Summary: "🍎 Rachel & Adabelle", + Start: "13:45", + End: "14:00", + Days: []time.Weekday{time.Wednesday}, + Tags: map[string]string{ + "week": "B", + }, + Zoom: "https://us02web.zoom.us/j/3839164321?pwd=aDNUdzN3bjlZbG93NlNqd09hU2xmUT09", + Students: []string{ + "adabelle.pratt@heliosns.org", + }, }, }, } diff --git a/main.go b/main.go index d6bc4ab..bcc736a 100644 --- a/main.go +++ b/main.go @@ -2,163 +2,18 @@ package main import ( "flag" - "io/ioutil" "log" - "time" - - "golang.org/x/oauth2/google" - "google.golang.org/api/calendar/v3" ) -var calName = flag.String("calendar", "", "name of calendar") -var days = flag.Int("days", 0, "number of days in the future") +var _days = flag.Int("days", 0, "number of days in the future") func main() { flag.Parse() - b, err := ioutil.ReadFile("credentials.json") - if err != nil { - log.Fatal(err) - } - - // If modifying these scopes, delete your previously saved token.json. - config, err := google.ConfigFromJSON(b, calendar.CalendarScope) - if err != nil { - log.Fatal(err) - } - client, err := getClient(config) - if err != nil { - log.Fatal(err) - } - - srv, err := calendar.New(client) - if err != nil { - log.Fatal(err) - } - - calendars, err := srv.CalendarList.List().Do() - if err != nil { - log.Fatal(err) - } - var myCal *calendar.CalendarListEntry - for _, item := range calendars.Items { - if item.Summary == *calName { - myCal = item - break - } - } - if myCal == nil { - log.Fatalf("calendar '%s' not found", *calName) - } - - loc, err := time.LoadLocation("US/Pacific") - if err != nil { - log.Fatal(err) - } - - now := time.Now().In(loc) - - for i := 0; i < *days; i++ { - date := now.Add(time.Duration(i * 24) * time.Hour) - - eventMap, err := getDayEvents(srv, myCal.Id, date) + for calName, classList := range classes { + err := updateCalendar(calName, classList, *_days) if err != nil { log.Fatal(err) } - - for _, class := range classes { - if !class.happensOnDay(date) { - continue - } - - if !class.tagsMatch(date) { - continue - } - - ev := class.buildEvent(date) - - old := eventMap[ev.Summary] - - if old == nil { - log.Printf("add: [%s] %s", ev.Start.DateTime, ev.Summary) - _, err := srv.Events.Insert(myCal.Id, ev).Do() - if err != nil { - log.Fatal(err) - } - } else { - if !eventsEqual(old, ev) { - log.Printf("upd: [%s] %s", ev.Start.DateTime, ev.Summary) - _, err := srv.Events.Update(myCal.Id, old.Id, ev).Do() - if err != nil { - log.Fatal(err) - } - } - } - } } } - -func getDayEvents(srv *calendar.Service, calendarId string, t time.Time) (map[string]*calendar.Event, error) { - start := time.Date( - t.Year(), - t.Month(), - t.Day(), - 0, 0, 0, 0, - t.Location()) - - // This may be wrong for DST changes, but it's the middle of the night and probably doesn't matter - end := start.Add(24 * time.Hour) - - events, err := srv.Events.List(calendarId). - ShowDeleted(false). - SingleEvents(true). - TimeMin(start.Format(time.RFC3339)). - TimeMax(end.Format(time.RFC3339)). - MaxResults(250). - Do() - if err != nil { - return nil, err - } - - eventMap := map[string]*calendar.Event{} - - for _, item := range events.Items { - eventMap[item.Summary] = item - } - - return eventMap, nil -} - -func eventsEqual(a *calendar.Event, b *calendar.Event) bool { - if a.Summary != b.Summary { - return false - } - - if a.Description != b.Description { - return false - } - - if a.Start.DateTime[:19] != b.Start.DateTime[:19] { - return false - } - - if a.End.DateTime[:19] != b.End.DateTime[:19] { - return false - } - - if len(a.Attendees) != len(b.Attendees) { - return false - } - - emails := map[string]bool{} - for _, attendee := range a.Attendees { - emails[attendee.Email] = true - } - for _, attendee := range b.Attendees { - if !emails[attendee.Email] { - return false - } - } - - return true -}