mirror of
				https://github.com/taigrr/wtf
				synced 2025-01-18 04:03:14 -08:00 
			
		
		
		
	Merge pull request #241 from senorprogrammer/revert-234-google-oauth
Revert "gcal module improvements"
This commit is contained in:
		
						commit
						01d664c058
					
				
							
								
								
									
										106
									
								
								gcal/client.go
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								gcal/client.go
									
									
									
									
									
								
							| @ -8,28 +8,43 @@ | |||||||
| package gcal | package gcal | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"os" | ||||||
|  | 	"os/user" | ||||||
|  | 	"path/filepath" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/senorprogrammer/wtf/wtf" | 	"github.com/senorprogrammer/wtf/wtf" | ||||||
|  | 	"golang.org/x/oauth2" | ||||||
|  | 	"golang.org/x/oauth2/google" | ||||||
| 	"google.golang.org/api/calendar/v3" | 	"google.golang.org/api/calendar/v3" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| /* -------------------- Exported Functions -------------------- */ | /* -------------------- Exported Functions -------------------- */ | ||||||
| 
 | 
 | ||||||
| func Fetch(oauthHandler OAuthUIHandler) ([]*CalEvent, error) { | func Fetch() ([]*CalEvent, error) { | ||||||
| 	secretFile, _ := wtf.ExpandHomeDir(wtf.Config.UString("wtf.mods.gcal.secretFile")) | 	ctx := context.Background() | ||||||
| 	tokenFile, _ := wtf.ExpandHomeDir(wtf.Config.UString("wtf.mods.gcal.tokenFile", "~/.gcal.token")) | 
 | ||||||
| 	config := OAuthClientConfig{ | 	secretPath, _ := wtf.ExpandHomeDir(wtf.Config.UString("wtf.mods.gcal.secretFile")) | ||||||
| 		SecretFile: secretFile, | 
 | ||||||
| 		TokenFile:  tokenFile, | 	b, err := ioutil.ReadFile(secretPath) | ||||||
| 		Scope:      calendar.CalendarReadonlyScope, |  | ||||||
| 	} |  | ||||||
| 	client, err := BuildOAuthClient(config, oauthHandler) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	config, err := google.ConfigFromJSON(b, calendar.CalendarReadonlyScope) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	client := getClient(ctx, config) | ||||||
|  | 
 | ||||||
| 	srv, err := calendar.New(client) | 	srv, err := calendar.New(client) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @ -85,6 +100,79 @@ func fromMidnight() time.Time { | |||||||
| 	return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) | 	return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // getClient uses a Context and Config to retrieve a Token | ||||||
|  | // then generate a Client. It returns the generated Client. | ||||||
|  | func getClient(ctx context.Context, config *oauth2.Config) *http.Client { | ||||||
|  | 	cacheFile, err := tokenCacheFile() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Unable to get path to cached credential file. %v", err) | ||||||
|  | 	} | ||||||
|  | 	tok, err := tokenFromFile(cacheFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		tok = getTokenFromWeb(config) | ||||||
|  | 		saveToken(cacheFile, tok) | ||||||
|  | 	} | ||||||
|  | 	return config.Client(ctx, tok) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // getTokenFromWeb uses Config to request a Token. | ||||||
|  | // It 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 code string | ||||||
|  | 	if _, err := fmt.Scan(&code); err != nil { | ||||||
|  | 		log.Fatalf("Unable to read authorization code %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tok, err := config.Exchange(oauth2.NoContext, code) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Unable to retrieve token from web %v", err) | ||||||
|  | 	} | ||||||
|  | 	return tok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // tokenCacheFile generates credential file path/filename. | ||||||
|  | // It returns the generated credential path/filename. | ||||||
|  | func tokenCacheFile() (string, error) { | ||||||
|  | 	usr, err := user.Current() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials") | ||||||
|  | 	os.MkdirAll(tokenCacheDir, 0700) | ||||||
|  | 	return filepath.Join(tokenCacheDir, | ||||||
|  | 		url.QueryEscape("calendar-go-quickstart.json")), err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // tokenFromFile retrieves a Token from a given file path. | ||||||
|  | // It returns the retrieved Token and any read error encountered. | ||||||
|  | func tokenFromFile(file string) (*oauth2.Token, error) { | ||||||
|  | 	f, err := os.Open(file) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	t := &oauth2.Token{} | ||||||
|  | 	err = json.NewDecoder(f).Decode(t) | ||||||
|  | 	defer f.Close() | ||||||
|  | 	return t, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // saveToken uses a file path to create a file and store the | ||||||
|  | // token in it. | ||||||
|  | func saveToken(file string, token *oauth2.Token) { | ||||||
|  | 	fmt.Printf("Saving credential file to: %s\n", file) | ||||||
|  | 	f, err := os.OpenFile(file, 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 getCalendarIdList(srv *calendar.Service) ([]string, error) { | func getCalendarIdList(srv *calendar.Service) ([]string, error) { | ||||||
| 	// Return single calendar if settings specify we should | 	// Return single calendar if settings specify we should | ||||||
| 	if !wtf.Config.UBool("wtf.mods.gcal.multiCalendar", false) { | 	if !wtf.Config.UBool("wtf.mods.gcal.multiCalendar", false) { | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								gcal/google.go
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								gcal/google.go
									
									
									
									
									
								
							| @ -1,107 +0,0 @@ | |||||||
| package gcal |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 
 |  | ||||||
| 	"github.com/senorprogrammer/wtf/logger" |  | ||||||
| 	"golang.org/x/oauth2" |  | ||||||
| 	"golang.org/x/oauth2/google" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // OAuthClientConfig describes Google oauth2 client configuration |  | ||||||
| type OAuthClientConfig struct { |  | ||||||
| 	SecretFile string |  | ||||||
| 	TokenFile  string |  | ||||||
| 	Scope      string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // OAuthUIHandler returns oauth2 authorization code, implements user interaction |  | ||||||
| type OAuthUIHandler = func(string, chan string) |  | ||||||
| 
 |  | ||||||
| func BuildOAuthClient(config OAuthClientConfig, handler OAuthUIHandler) (*http.Client, error) { |  | ||||||
| 	ctx := context.Background() |  | ||||||
| 
 |  | ||||||
| 	b, err := ioutil.ReadFile(config.SecretFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		logger.Log(fmt.Sprintf("Invalid secret file provided (%s): %v", config.SecretFile, err)) |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	oauthConfig, err := google.ConfigFromJSON(b, config.Scope) |  | ||||||
| 	if err != nil { |  | ||||||
| 		logger.Log(fmt.Sprintf("Secret file is not readable as JSON (%s): %v", config.SecretFile, err)) |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	tok, err := tokenFromFile(config.TokenFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		result := make(chan string) |  | ||||||
| 		go handler(oauthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline), result) |  | ||||||
| 		code, ok := <-result |  | ||||||
| 		if !ok { |  | ||||||
| 			// Cancelled |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		tok, err = oauthConfig.Exchange(oauth2.NoContext, code) |  | ||||||
| 		if err != nil { |  | ||||||
| 			logger.Log(fmt.Sprintf("Invalid exchange code provided (%s): %v", code, err)) |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		err = saveToken(config.TokenFile, tok) |  | ||||||
| 		if err != nil { |  | ||||||
| 			logger.Log(fmt.Sprintf("Unable to store oauth token (%s): %v", config.TokenFile, err)) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	} |  | ||||||
| 	return oauthConfig.Client(ctx, tok), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // CreateCodeInputDialog shows oauth2 code input dialog |  | ||||||
| func CreateCodeInputDialog(title string, widget *Widget) OAuthUIHandler { |  | ||||||
| 	return func(url string, result chan string) { |  | ||||||
| 		readInput := func() { |  | ||||||
| 			fmt.Printf("\r\nOAuth authorization [%s]\r\n", title) |  | ||||||
| 			fmt.Printf("Please open the following URL in your browser and paste authorization code below:\r\n") |  | ||||||
| 			fmt.Printf("%s\r\nAuthorization code: ", url) |  | ||||||
| 			var code string |  | ||||||
| 			if _, err := fmt.Scan(&code); err != nil { |  | ||||||
| 				logger.Log(fmt.Sprintf("Unable to read authorization code: %v", err)) |  | ||||||
| 				close(result) |  | ||||||
| 			} else { |  | ||||||
| 				result <- code |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		widget.app.Suspend(readInput) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // tokenFromFile retrieves a Token from a given file path. |  | ||||||
| // It returns the retrieved Token and any read error encountered. |  | ||||||
| func tokenFromFile(file string) (*oauth2.Token, error) { |  | ||||||
| 	f, err := os.Open(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	t := &oauth2.Token{} |  | ||||||
| 	err = json.NewDecoder(f).Decode(t) |  | ||||||
| 	defer f.Close() |  | ||||||
| 	return t, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // saveToken uses a file path to create a file and store the |  | ||||||
| // token in it. |  | ||||||
| func saveToken(file string, token *oauth2.Token) error { |  | ||||||
| 	// fmt.Printf("Saving credential file to: %s\n", file) |  | ||||||
| 	f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer f.Close() |  | ||||||
| 	json.NewEncoder(f).Encode(token) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @ -4,7 +4,6 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/rivo/tview" |  | ||||||
| 	"github.com/senorprogrammer/wtf/wtf" | 	"github.com/senorprogrammer/wtf/wtf" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -14,13 +13,11 @@ type Widget struct { | |||||||
| 	calEvents []*CalEvent | 	calEvents []*CalEvent | ||||||
| 	ch        chan struct{} | 	ch        chan struct{} | ||||||
| 	mutex     sync.Mutex | 	mutex     sync.Mutex | ||||||
| 	app       *tview.Application |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewWidget(app *tview.Application) *Widget { | func NewWidget() *Widget { | ||||||
| 	widget := Widget{ | 	widget := Widget{ | ||||||
| 		TextWidget: wtf.NewTextWidget(" Calendar ", "gcal", true), | 		TextWidget: wtf.NewTextWidget(" Calendar ", "gcal", false), | ||||||
| 		app:        app, |  | ||||||
| 		ch:         make(chan struct{}), | 		ch:         make(chan struct{}), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -37,7 +34,7 @@ func (widget *Widget) Disable() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (widget *Widget) Refresh() { | func (widget *Widget) Refresh() { | ||||||
| 	calEvents, err := Fetch(CreateCodeInputDialog(" Calendar ", widget)) | 	calEvents, err := Fetch() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		widget.calEvents = []*CalEvent{} | 		widget.calEvents = []*CalEvent{} | ||||||
| 	} else { | 	} else { | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								wtf.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								wtf.go
									
									
									
									
									
								
							| @ -180,7 +180,7 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { | |||||||
| 	case "cryptolive": | 	case "cryptolive": | ||||||
| 		Widgets = append(Widgets, cryptolive.NewWidget()) | 		Widgets = append(Widgets, cryptolive.NewWidget()) | ||||||
| 	case "gcal": | 	case "gcal": | ||||||
| 		Widgets = append(Widgets, gcal.NewWidget(app)) | 		Widgets = append(Widgets, gcal.NewWidget()) | ||||||
| 	case "gerrit": | 	case "gerrit": | ||||||
| 		Widgets = append(Widgets, gerrit.NewWidget(app, pages)) | 		Widgets = append(Widgets, gerrit.NewWidget(app, pages)) | ||||||
| 	case "git": | 	case "git": | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user