-
Notifications
You must be signed in to change notification settings - Fork 0
/
calendar.go
156 lines (142 loc) · 4.47 KB
/
calendar.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2021 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/calendar/v3"
"google.golang.org/api/option"
)
// Calendar interfaces with the Google Calendar API with an OAuth2 service
// account and a specific user token.
type Calendar struct {
srv *calendar.Service
}
// NewCalendar returns an initialized calendar client.
func NewCalendar(ctx context.Context, credentialsFile, tokenFile string) (*Calendar, error) {
// https://developers.google.com/workspace/guides/create-credentials
//#nosec G304
b, err := os.ReadFile(credentialsFile)
if err != nil {
return nil, fmt.Errorf("unable to read client secret file: %w", err)
}
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, calendar.CalendarEventsReadonlyScope)
if err != nil {
return nil, fmt.Errorf("unable to parse client secret file to config: %w", err)
}
client, err := getGoogleAPIClient(ctx, tokenFile, config)
if err != nil {
return nil, err
}
srv, err := calendar.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
return nil, fmt.Errorf("unable to retrieve Calendar client: %w", err)
}
return &Calendar{srv: srv}, nil
}
func (c *Calendar) GetCalendars(ctx context.Context) (*calendar.CalendarList, error) {
return c.srv.CalendarList.List().Do()
}
// Event is a simplified calendar event.
type Event struct {
summary string
status string
start time.Time
end time.Time
}
// GetEvents returns the next events starting between start and for the
// duration (exclusive).
func (c *Calendar) GetEvents(ctx context.Context, start time.Time, d time.Duration, id string) ([]Event, error) {
t1 := start.Format(time.RFC3339)
t2 := start.Add(d).Format(time.RFC3339)
events, err := c.srv.Events.List(id).ShowDeleted(false).
SingleEvents(true).TimeMin(t1).TimeMax(t2).MaxResults(10).OrderBy("startTime").Do()
if err != nil {
return nil, fmt.Errorf("unable to retrieve next ten of the user's events: %w", err)
}
out := make([]Event, 0, len(events.Items))
for _, item := range events.Items {
if item.Status == "cancelled" {
continue
}
start := item.Start.DateTime
if start == "" {
// Ignore all day event. They only have item.Start.Date set.
continue
}
s, err := time.Parse(time.RFC3339, item.Start.DateTime)
if err != nil {
return out, err
}
e, err := time.Parse(time.RFC3339, item.End.DateTime)
if err != nil {
return out, err
}
out = append(out, Event{
summary: item.Summary,
status: item.Status,
start: s,
end: e,
})
}
return out, nil
}
// getGoogleAPIClient retrieves a token, saves the token, then returns the generated
// client.
func getGoogleAPIClient(ctx context.Context, tf string, config *oauth2.Config) (*http.Client, error) {
tok, err := getOauth2TokenFromFile(tf)
if err != nil {
if tok, err = getOauth2TokenFromWeb(config); err != nil {
return nil, err
}
if err = saveToken(tf, tok); err != nil {
return nil, err
}
}
return config.Client(ctx, tok), nil
}
// getOauth2TokenFromWeb requests a token from the web, then returns the retrieved
// token.
func getOauth2TokenFromWeb(config *oauth2.Config) (*oauth2.Token, error) {
u := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following URL in your browser then type the authorization code: \n%v\n", u)
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
return nil, fmt.Errorf("unable to read authorization code: %w", err)
}
tok, err := config.Exchange(context.TODO(), authCode)
if err != nil {
return nil, fmt.Errorf("unable to retrieve token from web: %w", err)
}
return tok, nil
}
// getOauth2TokenFromFile retrieves a token from a local file.
func getOauth2TokenFromFile(tf string) (*oauth2.Token, error) {
//#nosec G304
d, err := os.ReadFile(tf)
if err == nil {
tok := &oauth2.Token{}
if err = json.Unmarshal(d, tok); err == nil {
return tok, nil
}
}
return nil, err
}
// saveToken saves a token to a file path.
func saveToken(tf string, token *oauth2.Token) error {
//#nosec G304
f, err := os.OpenFile(tf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return fmt.Errorf("unable to cache oauth token: %w", err)
}
defer f.Close()
return json.NewEncoder(f).Encode(token)
}