package main import ( "context" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" "time" "github.com/robfig/cron/v3" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/option" "google.golang.org/api/sheets/v4" ) type Config struct { SpreadsheetID string `json:"spreadsheetID,omitempty"` Range string `json:"range,omitempty"` Jobs []Jobs `json:"jobs,omitempty"` } type Jobs struct { Schedule string `json:"schedule,omitempty"` Values []interface{} `json:"values,omitempty"` } // Retrieve a token, saves the token, then returns the generated client. func getClient(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. tokFile := "token.json" tok, err := tokenFromFile(tokFile) if err != nil { tok = getTokenFromWeb(config) saveToken(tokFile, tok) } return config.Client(context.Background(), 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("Go to the following link in your browser then type the "+ "authorization code: \n%v\n", authURL) var authCode string 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) { 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 } // 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) } func writeExpense(client *http.Client, ctx context.Context, spreadsheetID string, tableRange string, values []interface{}) { srv, err := sheets.NewService(ctx, option.WithHTTPClient(client)) if err != nil { log.Fatalf("Unable to retrieve Sheets client: %v", err) } valueInputOption := "USER_ENTERED" insertDataOption := "INSERT_ROWS" rb := &sheets.ValueRange{ MajorDimension: "ROWS", } v := []interface{}{time.Now().Format("2006-01-02")} v = append(v, values...) rb.Values = append(rb.Values, v) resp, err := srv.Spreadsheets.Values.Append(spreadsheetID, tableRange, rb). ValueInputOption(valueInputOption).InsertDataOption(insertDataOption).Context(ctx).Do() if err != nil { log.Fatal(err) } log.Printf("API responded with %d for job: %s\n", resp.ServerResponse.HTTPStatusCode, v[1]) } func main() { // Get config var config Config jsonFile, err := os.Open("config.json") if err != nil { panic("config file read: " + err.Error()) } byteValue, _ := ioutil.ReadAll(jsonFile) jsonFile.Close() err = json.Unmarshal(byteValue, &config) if err != nil { panic("config json unmarshal" + err.Error()) } // Create sheets client ctx := context.Background() b, err := ioutil.ReadFile("credentials.json") if err != nil { log.Fatalf("Unable to read client secret file: %v", err) } conf, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets") if err != nil { log.Fatalf("Unable to parse client secret file to config: %v", err) } client := getClient(conf) // Schedule crons c := cron.New() for _, job := range config.Jobs { if _, err := c.AddFunc(job.Schedule, func() { writeExpense(client, ctx, config.SpreadsheetID, config.Range, job.Values) }); err != nil { log.Fatalln(err.Error()) } } c.Start() for { time.Sleep(time.Second) } // ctx := context.Background() // b, err := ioutil.ReadFile("credentials.json") // if err != nil { // log.Fatalf("Unable to read client secret file: %v", err) // } // // If modifying these scopes, delete your previously saved token.json. // config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets") // if err != nil { // log.Fatalf("Unable to parse client secret file to config: %v", err) // } // client := getClient(config) // srv, err := sheets.NewService(ctx, option.WithHTTPClient(client)) // if err != nil { // log.Fatalf("Unable to retrieve Sheets client: %v", err) // } // // Prints the names and majors of students in a sample spreadsheet: // // https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit // // spreadsheetId := "1TT4oJ7B_Lq4quyizxK2DxGR_qhYtaG4Mn40j2MNpB6E" // spreadsheetId := "1JpPdBi0xHpbfxtm5h1V7kQKzH_WPiIGuID8nQygk5o8" // readRange := "Expenses!A2:E" // valueInputOption := "USER_ENTERED" // insertDataOption := "INSERT_ROWS" // rb := &sheets.ValueRange{ // MajorDimension: "ROWS", // } // row := []interface{}{time.Now().Format("2006-01-02"), "Testrecord", "Konto", "Fixkosten", 80.23} // rb.Values = append(rb.Values, row) // resp, err := srv.Spreadsheets.Values.Append(spreadsheetId, readRange, rb). // ValueInputOption(valueInputOption).InsertDataOption(insertDataOption).Context(ctx).Do() // if err != nil { // log.Fatal(err) // } // // TODO: Change code below to process the `resp` object: // fmt.Printf("%#v\n", resp) // resp1, err := srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do() // if err != nil { // log.Fatalf("Unable to retrieve data from sheet: %v", err) // } // if len(resp1.Values) == 0 { // fmt.Println("No data found.") // } else { // fmt.Println("Date, Description, Account, Category, Expense:") // for _, row := range resp1.Values { // // Print columns A and E, which correspond to indices 0 and 4. // fmt.Printf("%s, %s, %s, %s, %s\n", row[0], row[1], row[2], row[3], row[4]) // } // } }