api.go (view raw)
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "log"
9 "net/http"
10 "os"
11
12 "path/filepath"
13 "time"
14
15 "golang.org/x/oauth2"
16 "golang.org/x/oauth2/google"
17 "google.golang.org/api/drive/v3"
18 "google.golang.org/api/option"
19 "google.golang.org/api/sheets/v4"
20)
21
22type GoogleAPI struct {
23 Ctx context.Context
24 Client *http.Client
25}
26
27type Entry struct {
28 FileID string `json:"id"`
29 Month string `json:"date"`
30 Name string `json:"name"`
31 FilePath string `json:"content"`
32}
33
34// Retrieve a token, saves the token, then returns the generated client.
35func getClient(config *oauth2.Config) *http.Client {
36 // The file token.json stores the user's access and refresh tokens, and is
37 // created automatically when the authorization flow completes for the first
38 // time.
39 tokFile := "token.json"
40 tok, err := tokenFromFile(tokFile)
41 if err != nil {
42 tok = getTokenFromWeb(config)
43 saveToken(tokFile, tok)
44 }
45 return config.Client(context.Background(), tok)
46}
47
48// Request a token from the web, then returns the retrieved token.
49func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
50 authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
51 fmt.Printf("Go to the following link in your browser then type the "+
52 "authorization code: \n%v\n", authURL)
53
54 var authCode string
55 if _, err := fmt.Scan(&authCode); err != nil {
56 log.Fatalf("Unable to read authorization code %v", err)
57 }
58
59 tok, err := config.Exchange(context.TODO(), authCode)
60 if err != nil {
61 log.Fatalf("Unable to retrieve token from web %v", err)
62 }
63 return tok
64}
65
66// Retrieves a token from a local file.
67func tokenFromFile(file string) (*oauth2.Token, error) {
68 f, err := os.Open(file)
69 if err != nil {
70 return nil, err
71 }
72 defer f.Close()
73 tok := &oauth2.Token{}
74 err = json.NewDecoder(f).Decode(tok)
75 return tok, err
76}
77
78// Saves a token to a file path.
79func saveToken(path string, token *oauth2.Token) {
80 fmt.Printf("Saving credential file to: %s\n", path)
81 f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
82 if err != nil {
83 log.Fatalf("Unable to cache oauth token: %v", err)
84 }
85 defer f.Close()
86 json.NewEncoder(f).Encode(token)
87}
88
89func initGoogleAPI() *GoogleAPI {
90 ctx := context.Background()
91 b, err := os.ReadFile("credentials.json")
92 if err != nil {
93 log.Fatalf("Unable to read client secret file: %v", err)
94 }
95
96 // If modifying these scopes, delete your previously saved token.json.
97 config, err := google.ConfigFromJSON(b, drive.DriveReadonlyScope, sheets.SpreadsheetsReadonlyScope)
98 if err != nil {
99 log.Fatalf("Unable to parse client secret file to config: %v", err)
100 }
101 client := getClient(config)
102 return &GoogleAPI{ctx, client}
103}
104
105func getEntries(googleApi *GoogleAPI) ([]Entry, error) {
106 srv, err := sheets.NewService(googleApi.Ctx, option.WithHTTPClient(googleApi.Client))
107 if err != nil {
108 log.Fatalf("Unable to retrieve Sheets client: %v", err)
109 return nil, err
110 }
111
112 resp, err := srv.Spreadsheets.Values.Get(spreadsheetId, spreadsheetRange).Do()
113 if err != nil {
114 log.Fatalf("Unable to retrieve data from sheet: %v", err)
115 return nil, err
116 }
117
118 values := resp.Values
119 rows := make([]Entry, len(values))
120 for i, row := range values {
121 dateString := row[0].(string)
122 date, err := time.Parse("02/01/2006 15.04.05", dateString)
123 if err != nil {
124 log.Println("Error while parsing the following time and date string:", dateString)
125 date = time.Now()
126 }
127 rows[i] = Entry{row[3].(string)[33:], date.Format("2006-01"), row[1].(string), ""}
128 }
129 return rows, nil
130}
131
132func getFile(googleApi *GoogleAPI, fileID string, cachePath string) (string, error) {
133 srv, err := drive.NewService(googleApi.Ctx, option.WithHTTPClient(googleApi.Client))
134 if err != nil {
135 log.Fatalf("Unable to retrieve Drive client: %v", err)
136 }
137
138 fileInfo, err := srv.Files.Get(fileID).Fields("name").Do()
139 if err != nil {
140 return "", err
141 }
142 fileExt := filepath.Ext(fileInfo.Name)
143
144 fileName := fileID + fileExt
145 filePath := filepath.Join(cachePath, fileName)
146 out, err := os.Create(filePath)
147 if err != nil {
148 return "", err
149 }
150 defer out.Close()
151
152 res, err := srv.Files.Get(fileID).Download()
153 if err != nil {
154 return "", err
155 }
156 defer res.Body.Close()
157
158 _, err = io.Copy(out, res.Body)
159 if err != nil {
160 return "", err
161 }
162
163 return fileExt, nil
164}