package calendar import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "strings" "time" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/calendar/v3" "google.golang.org/api/option" ) const tokFile = "token.json" type Service struct { calIds map[string]struct{} *calendar.Service } func NewService(ctx context.Context, credJson string, opts ...Option) (*Service, error) { b := []byte(credJson) 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.NewService(ctx, option.WithHTTPClient(client)) if err != nil { log.Fatalf("Unable to retrieve Calendar client: %v", err) } s := &Service{ Service: srv, calIds: make(map[string]struct{}), } for _, opt := range opts { err := opt(s) if err != nil { return nil, err } } return &Service{Service: srv}, nil } func (s *Service) Create(calendarId, name, description string, from, to time.Time) error { // add calendarId to list of known calendars _, ok := s.calIds[calendarId] if !ok { _, err := s.CalendarList.Get(calendarId).Do() if err != nil { return fmt.Errorf("cannot find calendar entry with id %q: %w", calendarId, err) } s.calIds[calendarId] = struct{}{} } ne, err := s.Events.Insert(calendarId, &calendar.Event{ Description: name, End: &calendar.EventDateTime{ Date: to.Format(time.DateOnly), }, Start: &calendar.EventDateTime{ Date: from.Format(time.DateOnly), }, Summary: description, }).Do() log.Println(err) log.Printf("%+v: %s", ne.Summary, ne.Id) return err } func (s *Service) List(from, to time.Time) (*calendar.Events, error) { return nil, nil } func (s *Service) Delete(id int) error { return nil } // Retrieve a token, saves the token, then returns the generated client. func getClient(ctx context.Context, config *oauth2.Config) *http.Client { // The file token.json stores the user's access and refresh tokens, and is // created automatically when the authorization flow completes for the first // time. // tok, err := tokenFromFile(tokFile) tok, err := tokenFromEnv() if err != nil { tok = getTokenFromWeb(config) saveToken(tokFile, tok) } return config.Client(ctx, tok) } // Request a token from the web, then returns the retrieved token. func getTokenFromWeb(config *oauth2.Config) *oauth2.Token { authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) fmt.Printf("Click on the following link to open in your browser \n%v\n", authURL) // TODO: write a callback endpoint that will parse the auth code from the URL var authCode string fmt.Print("Type the authorization code: \n> ") if _, err := fmt.Scan(&authCode); err != nil { log.Fatalf("Unable to read authorization code: %v", err) } tok, err := config.Exchange(context.TODO(), authCode) if err != nil { log.Fatalf("Unable to retrieve token from web: %v", err) } return tok } // Retrieves a token from a local file. func tokenFromFile(file string) (*oauth2.Token, error) { //nolint:unused f, err := os.Open(file) if err != nil { return nil, err } defer f.Close() tok := &oauth2.Token{} err = json.NewDecoder(f).Decode(tok) return tok, err } func tokenFromEnv() (*oauth2.Token, error) { t := os.Getenv("CALENDAR_TOKEN") f := strings.NewReader(t) tok := &oauth2.Token{} err := json.NewDecoder(f).Decode(tok) return tok, err } // Saves a token to a file path. func saveToken(path string, token *oauth2.Token) { fmt.Printf("Saving credential file to: %s\n", path) f, err := os.OpenFile(path, 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) }