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 settings *Settings client *Client items []Item archivedView bool } func NewWidget(tviewApp *tview.Application, pages *tview.Pages, settings *Settings) *Widget { widget := Widget{ ScrollableWidget: view.NewScrollableWidget(tviewApp, nil, settings.Common), settings: settings, client: NewClient(settings.consumerKey, "http://localhost"), archivedView: false, } widget.CommonSettings() widget.SetRenderFunction(widget.Render) widget.View.SetScrollable(true) widget.View.SetRegions(true) 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 { 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 %w", 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 { 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.Colors.RowTheme.EvenForeground, widget.settings.Colors.RowTheme.EvenBackground text := item.ResolvedTitle if isSelected { foreColor = widget.settings.Colors.RowTheme.HighlightedForeground backColor = widget.settings.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 { 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 }