github.com/wtfutil/wtf@v0.43.0/modules/gspreadsheets/client.go (about)

     1  /*
     2  * This butt-ugly code is direct from Google itself
     3  * https://developers.google.com/sheets/api/quickstart/go
     4   */
     5  
     6  package gspreadsheets
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"log"
    13  	"net/http"
    14  	"net/url"
    15  	"os"
    16  	"os/user"
    17  	"path/filepath"
    18  
    19  	"github.com/wtfutil/wtf/utils"
    20  	"golang.org/x/oauth2"
    21  	"golang.org/x/oauth2/google"
    22  	"google.golang.org/api/option"
    23  	sheets "google.golang.org/api/sheets/v4"
    24  )
    25  
    26  /* -------------------- Exported Functions -------------------- */
    27  
    28  func (widget *Widget) Fetch() ([]*sheets.ValueRange, error) {
    29  	ctx := context.Background()
    30  
    31  	secretPath, _ := utils.ExpandHomeDir(widget.settings.secretFile)
    32  
    33  	b, err := os.ReadFile(filepath.Clean(secretPath))
    34  	if err != nil {
    35  		log.Fatalf("Unable to read secretPath. %v", err)
    36  		return nil, err
    37  	}
    38  
    39  	config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets.readonly")
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	client := getClient(ctx, config)
    45  
    46  	srv, err := sheets.NewService(context.Background(), option.WithHTTPClient(client))
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	cells := utils.ToStrs(widget.settings.cellAddresses)
    52  
    53  	responses := make([]*sheets.ValueRange, len(cells))
    54  
    55  	for i := 0; i < len(cells); i++ {
    56  		resp, getErr := srv.Spreadsheets.Values.Get(widget.settings.sheetID, cells[i]).Do()
    57  		if getErr != nil {
    58  			return nil, getErr
    59  		}
    60  		responses[i] = resp
    61  	}
    62  
    63  	return responses, err
    64  }
    65  
    66  /* -------------------- Unexported Functions -------------------- */
    67  
    68  // getClient uses a Context and Config to retrieve a Token
    69  // then generate a Client. It returns the generated Client.
    70  func getClient(ctx context.Context, config *oauth2.Config) *http.Client {
    71  	cacheFile, err := tokenCacheFile()
    72  	if err != nil {
    73  		log.Fatalf("Unable to get path to cached credential file. %v", err)
    74  	}
    75  	tok, err := tokenFromFile(cacheFile)
    76  	if err != nil {
    77  		tok = getTokenFromWeb(config)
    78  		saveToken(cacheFile, tok)
    79  	}
    80  	return config.Client(ctx, tok)
    81  }
    82  
    83  // getTokenFromWeb uses Config to request a Token.
    84  // It returns the retrieved Token.
    85  func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
    86  	authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
    87  	fmt.Printf("Go to the following link in your browser then type the "+
    88  		"authorization code: \n%v\n", authURL)
    89  
    90  	var code string
    91  	if _, err := fmt.Scan(&code); err != nil {
    92  		log.Fatalf("Unable to read authorization code %v", err)
    93  	}
    94  
    95  	tok, err := config.Exchange(context.Background(), code)
    96  	if err != nil {
    97  		log.Fatalf("Unable to retrieve token from web %v", err)
    98  	}
    99  	return tok
   100  }
   101  
   102  // tokenCacheFile generates credential file path/filename.
   103  // It returns the generated credential path/filename.
   104  func tokenCacheFile() (string, error) {
   105  	usr, err := user.Current()
   106  	if err != nil {
   107  		return "", err
   108  	}
   109  
   110  	tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials")
   111  	err = os.MkdirAll(tokenCacheDir, 0700)
   112  	if err != nil {
   113  		return "", err
   114  	}
   115  
   116  	return filepath.Join(tokenCacheDir, url.QueryEscape("spreadsheets-go-quickstart.json")), err
   117  }
   118  
   119  // tokenFromFile retrieves a Token from a given file path.
   120  // It returns the retrieved Token and any read error encountered.
   121  func tokenFromFile(file string) (*oauth2.Token, error) {
   122  	f, err := os.Open(filepath.Clean(file))
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	t := &oauth2.Token{}
   127  	err = json.NewDecoder(f).Decode(t)
   128  	defer func() { _ = f.Close() }()
   129  	return t, err
   130  }
   131  
   132  // saveToken uses a file path to create a file and store the
   133  // token in it.
   134  func saveToken(file string, token *oauth2.Token) {
   135  	fmt.Printf("Saving credential file to: %s\n", file)
   136  	f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
   137  	if err != nil {
   138  		log.Fatalf("Unable to cache oauth token: %v", err)
   139  	}
   140  	defer func() { _ = f.Close() }()
   141  
   142  	err = json.NewEncoder(f).Encode(token)
   143  	if err != nil {
   144  		log.Fatalf("Unable to encode oauth token: %v", err)
   145  	}
   146  }