mirror of
				https://github.com/taigrr/wtf
				synced 2025-01-18 04:03:14 -08:00 
			
		
		
		
	add pocket widget
This commit is contained in:
		
							parent
							
								
									251e3fe10d
								
							
						
					
					
						commit
						29d3a16491
					
				@ -40,6 +40,7 @@ import (
 | 
				
			|||||||
	"github.com/wtfutil/wtf/modules/newrelic"
 | 
						"github.com/wtfutil/wtf/modules/newrelic"
 | 
				
			||||||
	"github.com/wtfutil/wtf/modules/opsgenie"
 | 
						"github.com/wtfutil/wtf/modules/opsgenie"
 | 
				
			||||||
	"github.com/wtfutil/wtf/modules/pagerduty"
 | 
						"github.com/wtfutil/wtf/modules/pagerduty"
 | 
				
			||||||
 | 
						"github.com/wtfutil/wtf/modules/pocket"
 | 
				
			||||||
	"github.com/wtfutil/wtf/modules/power"
 | 
						"github.com/wtfutil/wtf/modules/power"
 | 
				
			||||||
	"github.com/wtfutil/wtf/modules/resourceusage"
 | 
						"github.com/wtfutil/wtf/modules/resourceusage"
 | 
				
			||||||
	"github.com/wtfutil/wtf/modules/rollbar"
 | 
						"github.com/wtfutil/wtf/modules/rollbar"
 | 
				
			||||||
@ -202,6 +203,9 @@ func MakeWidget(
 | 
				
			|||||||
	case "prettyweather":
 | 
						case "prettyweather":
 | 
				
			||||||
		settings := prettyweather.NewSettingsFromYAML(moduleName, moduleConfig, config)
 | 
							settings := prettyweather.NewSettingsFromYAML(moduleName, moduleConfig, config)
 | 
				
			||||||
		widget = prettyweather.NewWidget(app, settings)
 | 
							widget = prettyweather.NewWidget(app, settings)
 | 
				
			||||||
 | 
						case "pocket":
 | 
				
			||||||
 | 
							settings := pocket.NewSettingsFromYAML(moduleName, moduleConfig, config)
 | 
				
			||||||
 | 
							widget = pocket.NewWidget(app, pages, settings)
 | 
				
			||||||
	case "resourceusage":
 | 
						case "resourceusage":
 | 
				
			||||||
		settings := resourceusage.NewSettingsFromYAML(moduleName, moduleConfig, config)
 | 
							settings := resourceusage.NewSettingsFromYAML(moduleName, moduleConfig, config)
 | 
				
			||||||
		widget = resourceusage.NewWidget(app, settings)
 | 
							widget = resourceusage.NewWidget(app, settings)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										261
									
								
								modules/pocket/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								modules/pocket/client.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,261 @@
 | 
				
			|||||||
 | 
					package pocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Client pocket client Documention at https://getpocket.com/developer/docs/overview
 | 
				
			||||||
 | 
					type Client struct {
 | 
				
			||||||
 | 
						consumerKey string
 | 
				
			||||||
 | 
						accessToken *string
 | 
				
			||||||
 | 
						baseURL     string
 | 
				
			||||||
 | 
						redirectURL string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//NewClient returns a new PocketClient
 | 
				
			||||||
 | 
					func NewClient(consumerKey, redirectURL string) *Client {
 | 
				
			||||||
 | 
						return &Client{
 | 
				
			||||||
 | 
							consumerKey: consumerKey,
 | 
				
			||||||
 | 
							redirectURL: redirectURL,
 | 
				
			||||||
 | 
							baseURL:     "https://getpocket.com/v3",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Item represents link in pocket api
 | 
				
			||||||
 | 
					type Item struct {
 | 
				
			||||||
 | 
						ItemID                 string `json:"item_id"`
 | 
				
			||||||
 | 
						ResolvedID             string `json:"resolved_id"`
 | 
				
			||||||
 | 
						GivenURL               string `json:"given_url"`
 | 
				
			||||||
 | 
						GivenTitle             string `json:"given_title"`
 | 
				
			||||||
 | 
						Favorite               string `json:"favorite"`
 | 
				
			||||||
 | 
						Status                 string `json:"status"`
 | 
				
			||||||
 | 
						TimeAdded              string `json:"time_added"`
 | 
				
			||||||
 | 
						TimeUpdated            string `json:"time_updated"`
 | 
				
			||||||
 | 
						TimeRead               string `json:"time_read"`
 | 
				
			||||||
 | 
						TimeFavorited          string `json:"time_favorited"`
 | 
				
			||||||
 | 
						SortID                 int    `json:"sort_id"`
 | 
				
			||||||
 | 
						ResolvedTitle          string `json:"resolved_title"`
 | 
				
			||||||
 | 
						ResolvedURL            string `json:"resolved_url"`
 | 
				
			||||||
 | 
						Excerpt                string `json:"excerpt"`
 | 
				
			||||||
 | 
						IsArticle              string `json:"is_article"`
 | 
				
			||||||
 | 
						IsIndex                string `json:"is_index"`
 | 
				
			||||||
 | 
						HasVideo               string `json:"has_video"`
 | 
				
			||||||
 | 
						HasImage               string `json:"has_image"`
 | 
				
			||||||
 | 
						WordCount              string `json:"word_count"`
 | 
				
			||||||
 | 
						Lang                   string `json:"lang"`
 | 
				
			||||||
 | 
						TimeToRead             int    `json:"time_to_read"`
 | 
				
			||||||
 | 
						TopImageURL            string `json:"top_image_url"`
 | 
				
			||||||
 | 
						ListenDurationEstimate int    `json:"listen_duration_estimate"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//ItemLists represent list of links
 | 
				
			||||||
 | 
					type ItemLists struct {
 | 
				
			||||||
 | 
						Status   int             `json:"status"`
 | 
				
			||||||
 | 
						Complete int             `json:"complete"`
 | 
				
			||||||
 | 
						List     map[string]Item `json:"list"`
 | 
				
			||||||
 | 
						Since    int             `json:"since"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type request struct {
 | 
				
			||||||
 | 
						requestBody interface{}
 | 
				
			||||||
 | 
						method      string
 | 
				
			||||||
 | 
						result      interface{}
 | 
				
			||||||
 | 
						headers     map[string]string
 | 
				
			||||||
 | 
						url         string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (client *Client) request(req request, result interface{}) error {
 | 
				
			||||||
 | 
						jsonValues, err := json.Marshal(req.requestBody)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						request, err := http.NewRequest(req.method, req.url, bytes.NewBuffer(jsonValues))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for key, value := range req.headers {
 | 
				
			||||||
 | 
							request.Header.Add(key, value)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resp, err := http.DefaultClient.Do(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						responseBody, err := ioutil.ReadAll(resp.Body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if resp.StatusCode >= 400 {
 | 
				
			||||||
 | 
							return fmt.Errorf(`server responded with [%d]:%s,url:%s`, resp.StatusCode, responseBody, req.url)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := json.Unmarshal(responseBody, &result); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf(`Could not unmarshal url [%s] 
 | 
				
			||||||
 | 
							response [%s] request[%s] error:%v`, req.url, responseBody, jsonValues, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type obtainRequestTokenRequest struct {
 | 
				
			||||||
 | 
						ConsumerKey string `json:"consumer_key"`
 | 
				
			||||||
 | 
						RedirectURI string `json:"redirect_uri"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//ObtainRequestToken get request token to be used in the auth workflow
 | 
				
			||||||
 | 
					func (client *Client) ObtainRequestToken() (code string, err error) {
 | 
				
			||||||
 | 
						url := fmt.Sprintf("%s/oauth/request", client.baseURL)
 | 
				
			||||||
 | 
						requestData := obtainRequestTokenRequest{ConsumerKey: client.consumerKey, RedirectURI: client.redirectURL}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var responseData map[string]string
 | 
				
			||||||
 | 
						req := request{
 | 
				
			||||||
 | 
							method:      "POST",
 | 
				
			||||||
 | 
							url:         url,
 | 
				
			||||||
 | 
							requestBody: requestData,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.headers = map[string]string{
 | 
				
			||||||
 | 
							"X-Accept":     "application/json",
 | 
				
			||||||
 | 
							"Content-Type": "application/json",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = client.request(req, &responseData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return code, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return responseData["code"], nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//CreateAuthLink create authorization link to redirect the user to
 | 
				
			||||||
 | 
					func (client *Client) CreateAuthLink(requestToken string) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("https://getpocket.com/auth/authorize?request_token=%s&redirect_uri=%s", requestToken, client.redirectURL)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type accessTokenRequest struct {
 | 
				
			||||||
 | 
						ConsumerKey string `json:"consumer_key"`
 | 
				
			||||||
 | 
						RequestCode string `json:"code"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// accessTokenResponse represents
 | 
				
			||||||
 | 
					type accessTokenResponse struct {
 | 
				
			||||||
 | 
						AccessToken string `json:"access_token"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//GetAccessToken exchange request token for accesstoken
 | 
				
			||||||
 | 
					func (client *Client) GetAccessToken(requestToken string) (accessToken string, err error) {
 | 
				
			||||||
 | 
						url := fmt.Sprintf("%s/oauth/authorize", client.baseURL)
 | 
				
			||||||
 | 
						requestData := accessTokenRequest{
 | 
				
			||||||
 | 
							ConsumerKey: client.consumerKey,
 | 
				
			||||||
 | 
							RequestCode: requestToken,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req := request{
 | 
				
			||||||
 | 
							method:      "POST",
 | 
				
			||||||
 | 
							url:         url,
 | 
				
			||||||
 | 
							requestBody: requestData,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.headers = map[string]string{
 | 
				
			||||||
 | 
							"X-Accept":     "application/json",
 | 
				
			||||||
 | 
							"Content-Type": "application/json",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var response accessTokenResponse
 | 
				
			||||||
 | 
						err = client.request(req, &response)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return response.AccessToken, nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*LinkState  represents links states to be retrived
 | 
				
			||||||
 | 
					According to the api https://getpocket.com/developer/docs/v3/retrieve
 | 
				
			||||||
 | 
					there are 3 states:
 | 
				
			||||||
 | 
						1-archive
 | 
				
			||||||
 | 
						2-unread
 | 
				
			||||||
 | 
						3-all
 | 
				
			||||||
 | 
					however archive does not really well work and returns links that are in the
 | 
				
			||||||
 | 
					unread list
 | 
				
			||||||
 | 
					buy inspecting getpocket I found out that there is an undocumanted read state
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					type LinkState string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						//Read links that has been read (undocumanted)
 | 
				
			||||||
 | 
						Read LinkState = "read"
 | 
				
			||||||
 | 
						//Unread links has not been read
 | 
				
			||||||
 | 
						Unread LinkState = "unread"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLinks retrive links of a given states https://getpocket.com/developer/docs/v3/retrieve
 | 
				
			||||||
 | 
					func (client *Client) GetLinks(state LinkState) (response ItemLists, err error) {
 | 
				
			||||||
 | 
						url := fmt.Sprintf("%s/get?sort=newest&state=%s&consumer_key=%s&access_token=%s", client.baseURL, state, client.consumerKey, *client.accessToken)
 | 
				
			||||||
 | 
						req := request{
 | 
				
			||||||
 | 
							method: "GET",
 | 
				
			||||||
 | 
							url:    url,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.headers = map[string]string{
 | 
				
			||||||
 | 
							"X-Accept":     "application/json",
 | 
				
			||||||
 | 
							"Content-Type": "application/json",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = client.request(req, &response)
 | 
				
			||||||
 | 
						return response, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Action represents a mutation to link
 | 
				
			||||||
 | 
					type Action string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						//Archive to put the link in the archived list (read list)
 | 
				
			||||||
 | 
						Archive Action = "archive"
 | 
				
			||||||
 | 
						//ReAdd to put the link back in the to reed list
 | 
				
			||||||
 | 
						ReAdd Action = "readd"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type actionParams struct {
 | 
				
			||||||
 | 
						Action Action `json:"action"`
 | 
				
			||||||
 | 
						ItemID string `json:"item_id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//ModifyLink change the state of the link
 | 
				
			||||||
 | 
					func (client *Client) ModifyLink(action Action, itemID string) (ok bool, err error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						actions := []actionParams{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Action: action,
 | 
				
			||||||
 | 
								ItemID: itemID,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						urlActionParm, err := json.Marshal(actions)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						url := fmt.Sprintf("%s/send?consumer_key=%s&access_token=%s&actions=%s", client.baseURL, client.consumerKey, *client.accessToken, urlActionParm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req := request{
 | 
				
			||||||
 | 
							method: "GET",
 | 
				
			||||||
 | 
							url:    url,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = client.request(req, nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true, nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								modules/pocket/item_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								modules/pocket/item_service.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package pocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "sort"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type sortByTimeAdded []Item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a sortByTimeAdded) Len() int           { return len(a) }
 | 
				
			||||||
 | 
					func (a sortByTimeAdded) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
				
			||||||
 | 
					func (a sortByTimeAdded) Less(i, j int) bool { return a[i].TimeAdded > a[j].TimeAdded }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func orderItemResponseByKey(response ItemLists) []Item {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var items sortByTimeAdded
 | 
				
			||||||
 | 
						for _, v := range response.List {
 | 
				
			||||||
 | 
							items = append(items, v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sort.Sort(items)
 | 
				
			||||||
 | 
						return items
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								modules/pocket/keyboard.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								modules/pocket/keyboard.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					package pocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "github.com/gdamore/tcell"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) initializeKeyboardControls() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget.InitializeCommonControls(widget.Refresh)
 | 
				
			||||||
 | 
						widget.SetKeyboardChar("a", widget.toggleLink, "Toggle Link")
 | 
				
			||||||
 | 
						widget.SetKeyboardChar("t", widget.toggleView, "Toggle view (links ,archived links)")
 | 
				
			||||||
 | 
						widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select Next Link")
 | 
				
			||||||
 | 
						widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select Previous Link")
 | 
				
			||||||
 | 
						widget.SetKeyboardKey(tcell.KeyEnter, widget.openLink, "Open Link in the browser")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								modules/pocket/settings.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								modules/pocket/settings.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package pocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/olebedev/config"
 | 
				
			||||||
 | 
						"github.com/wtfutil/wtf/cfg"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						defaultFocusable = true
 | 
				
			||||||
 | 
						defaultTitle     = "Pocket"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Settings struct {
 | 
				
			||||||
 | 
						common      *cfg.Common
 | 
				
			||||||
 | 
						consumerKey string
 | 
				
			||||||
 | 
						requestKey  *string
 | 
				
			||||||
 | 
						accessToken *string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
 | 
				
			||||||
 | 
						settings := Settings{
 | 
				
			||||||
 | 
							common:      cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
 | 
				
			||||||
 | 
							consumerKey: ymlConfig.UString("consumerKey"),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &settings
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										232
									
								
								modules/pocket/widget.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								modules/pocket/widget.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,232 @@
 | 
				
			|||||||
 | 
					package pocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/rivo/tview"
 | 
				
			||||||
 | 
						"github.com/wtfutil/wtf/cfg"
 | 
				
			||||||
 | 
						"github.com/wtfutil/wtf/logger"
 | 
				
			||||||
 | 
						"github.com/wtfutil/wtf/utils"
 | 
				
			||||||
 | 
						"github.com/wtfutil/wtf/view"
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Widget struct {
 | 
				
			||||||
 | 
						view.ScrollableWidget
 | 
				
			||||||
 | 
						view.KeyboardWidget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						settings     *Settings
 | 
				
			||||||
 | 
						client       *Client
 | 
				
			||||||
 | 
						items        []Item
 | 
				
			||||||
 | 
						archivedView bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *Widget {
 | 
				
			||||||
 | 
						widget := Widget{
 | 
				
			||||||
 | 
							KeyboardWidget:   view.NewKeyboardWidget(app, pages, settings.common),
 | 
				
			||||||
 | 
							ScrollableWidget: view.NewScrollableWidget(app, settings.common),
 | 
				
			||||||
 | 
							settings:         settings,
 | 
				
			||||||
 | 
							client:           NewClient(settings.consumerKey, "http://localhost"),
 | 
				
			||||||
 | 
							archivedView:     false,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget.CommonSettings()
 | 
				
			||||||
 | 
						widget.View.SetInputCapture(widget.InputCapture)
 | 
				
			||||||
 | 
						widget.SetRenderFunction(widget.Render)
 | 
				
			||||||
 | 
						widget.View.SetScrollable(true)
 | 
				
			||||||
 | 
						widget.View.SetRegions(true)
 | 
				
			||||||
 | 
						widget.KeyboardWidget.SetView(widget.View)
 | 
				
			||||||
 | 
						widget.initializeKeyboardControls()
 | 
				
			||||||
 | 
						widget.Selected = -1
 | 
				
			||||||
 | 
						widget.SetItemCount(0)
 | 
				
			||||||
 | 
						return &widget
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -------------------- Exported Functions -------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) Render() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget.Redraw(widget.content)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) Refresh() {
 | 
				
			||||||
 | 
						if widget.client.accessToken == nil {
 | 
				
			||||||
 | 
							metaData, err := readMetaDataFromDisk()
 | 
				
			||||||
 | 
							if err != nil || metaData.AccessToken == nil {
 | 
				
			||||||
 | 
								widget.Redraw(widget.authorizeWorkFlow)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							widget.client.accessToken = metaData.AccessToken
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						state := Unread
 | 
				
			||||||
 | 
						if widget.archivedView == true {
 | 
				
			||||||
 | 
							state = Read
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						response, err := widget.client.GetLinks(state)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							widget.SetItemCount(0)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget.items = orderItemResponseByKey(response)
 | 
				
			||||||
 | 
						widget.SetItemCount(len(widget.items))
 | 
				
			||||||
 | 
						widget.Redraw(widget.content)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -------------------- Unexported Functions -------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type pocketMetaData struct {
 | 
				
			||||||
 | 
						AccessToken *string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func writeMetaDataToDisk(metaData pocketMetaData) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileData, err := yaml.Marshal(metaData)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Could not write token to disk %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wtfConfigDir, err := cfg.WtfConfigDir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						filePath := fmt.Sprintf("%s/%s", wtfConfigDir, "pocket.data")
 | 
				
			||||||
 | 
						err = ioutil.WriteFile(filePath, fileData, 0644)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readMetaDataFromDisk() (pocketMetaData, error) {
 | 
				
			||||||
 | 
						wtfConfigDir, err := cfg.WtfConfigDir()
 | 
				
			||||||
 | 
						var metaData pocketMetaData
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return metaData, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						filePath := fmt.Sprintf("%s/%s", wtfConfigDir, "pocket.data")
 | 
				
			||||||
 | 
						fileData, err := utils.ReadFileBytes(filePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return metaData, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = yaml.Unmarshal(fileData, &metaData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return metaData, err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
						Authorization workflow is documented at https://getpocket.com/developer/docs/authentication
 | 
				
			||||||
 | 
						broken to 4 steps :
 | 
				
			||||||
 | 
							1- Obtain a platform consumer key from http://getpocket.com/developer/apps/new.
 | 
				
			||||||
 | 
							2- Obtain a request token
 | 
				
			||||||
 | 
							3- Redirect user to Pocket to continue authorization
 | 
				
			||||||
 | 
							4- Receive the callback from Pocket, this wont be used
 | 
				
			||||||
 | 
							5- Convert a request token into a Pocket access token
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					func (widget *Widget) authorizeWorkFlow() (string, string, bool) {
 | 
				
			||||||
 | 
						title := widget.CommonSettings().Title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if widget.settings.requestKey == nil {
 | 
				
			||||||
 | 
							requestToken, err := widget.client.ObtainRequestToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logger.Log(err.Error())
 | 
				
			||||||
 | 
								return title, err.Error(), true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							widget.settings.requestKey = &requestToken
 | 
				
			||||||
 | 
							redirectURL := widget.client.CreateAuthLink(requestToken)
 | 
				
			||||||
 | 
							content := fmt.Sprintf("Please click on %s to Authorize the app", redirectURL)
 | 
				
			||||||
 | 
							return title, content, true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if widget.settings.accessToken == nil {
 | 
				
			||||||
 | 
							accessToken, err := widget.client.GetAccessToken(*widget.settings.requestKey)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logger.Log(err.Error())
 | 
				
			||||||
 | 
								redirectURL := widget.client.CreateAuthLink(*widget.settings.requestKey)
 | 
				
			||||||
 | 
								content := fmt.Sprintf("Please click on %s to Authorize the app", redirectURL)
 | 
				
			||||||
 | 
								return title, content, true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							content := "Authorized"
 | 
				
			||||||
 | 
							widget.settings.accessToken = &accessToken
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							metaData := pocketMetaData{
 | 
				
			||||||
 | 
								AccessToken: &accessToken,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = writeMetaDataToDisk(metaData)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								content = err.Error()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return title, content, true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						content := "Authorized"
 | 
				
			||||||
 | 
						return title, content, true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) toggleView() {
 | 
				
			||||||
 | 
						widget.archivedView = !widget.archivedView
 | 
				
			||||||
 | 
						widget.Refresh()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) openLink() {
 | 
				
			||||||
 | 
						sel := widget.GetSelected()
 | 
				
			||||||
 | 
						if sel >= 0 && widget.items != nil && sel < len(widget.items) {
 | 
				
			||||||
 | 
							item := &widget.items[sel]
 | 
				
			||||||
 | 
							utils.OpenFile(item.GivenURL)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) toggleLink() {
 | 
				
			||||||
 | 
						sel := widget.GetSelected()
 | 
				
			||||||
 | 
						action := Archive
 | 
				
			||||||
 | 
						if widget.archivedView == true {
 | 
				
			||||||
 | 
							action = ReAdd
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if sel >= 0 && widget.items != nil && sel < len(widget.items) {
 | 
				
			||||||
 | 
							item := &widget.items[sel]
 | 
				
			||||||
 | 
							_, err := widget.client.ModifyLink(action, item.ItemID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logger.Log(err.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						widget.Refresh()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) formatItem(item Item, isSelected bool) string {
 | 
				
			||||||
 | 
						foreColor, backColor := widget.settings.common.Colors.RowTheme.EvenForeground, widget.settings.common.Colors.RowTheme.EvenBackground
 | 
				
			||||||
 | 
						text := item.ResolvedTitle
 | 
				
			||||||
 | 
						if isSelected == true {
 | 
				
			||||||
 | 
							foreColor = widget.settings.common.Colors.RowTheme.HighlightedForeground
 | 
				
			||||||
 | 
							backColor = widget.settings.common.Colors.RowTheme.HighlightedBackground
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return fmt.Sprintf("[%s:%s]%s[white]", foreColor, backColor, tview.Escape(text))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (widget *Widget) content() (string, string, bool) {
 | 
				
			||||||
 | 
						title := widget.CommonSettings().Title
 | 
				
			||||||
 | 
						currentViewTitle := "Reading List"
 | 
				
			||||||
 | 
						if widget.archivedView == true {
 | 
				
			||||||
 | 
							currentViewTitle = "Archived list"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						title = fmt.Sprintf("%s-%s", title, currentViewTitle)
 | 
				
			||||||
 | 
						content := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, v := range widget.items {
 | 
				
			||||||
 | 
							content += widget.formatItem(v, i == widget.Selected) + "\n"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return title, content, false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user