github.com/wtfutil/wtf@v0.43.0/modules/pocket/widget.go (about) 1 package pocket 2 3 import ( 4 "fmt" 5 "os" 6 7 "github.com/rivo/tview" 8 "github.com/wtfutil/wtf/cfg" 9 "github.com/wtfutil/wtf/logger" 10 "github.com/wtfutil/wtf/utils" 11 "github.com/wtfutil/wtf/view" 12 "gopkg.in/yaml.v2" 13 ) 14 15 type Widget struct { 16 view.ScrollableWidget 17 18 settings *Settings 19 client *Client 20 items []Item 21 archivedView bool 22 } 23 24 func NewWidget(tviewApp *tview.Application, redrawChan chan bool, _ *tview.Pages, settings *Settings) *Widget { 25 widget := Widget{ 26 ScrollableWidget: view.NewScrollableWidget(tviewApp, redrawChan, nil, settings.Common), 27 settings: settings, 28 client: NewClient(settings.consumerKey, "http://localhost"), 29 archivedView: false, 30 } 31 32 widget.CommonSettings() 33 widget.SetRenderFunction(widget.Render) 34 widget.View.SetScrollable(true) 35 widget.View.SetRegions(true) 36 widget.initializeKeyboardControls() 37 widget.Selected = -1 38 widget.SetItemCount(0) 39 return &widget 40 } 41 42 /* -------------------- Exported Functions -------------------- */ 43 44 func (widget *Widget) Render() { 45 46 widget.Redraw(widget.content) 47 } 48 49 func (widget *Widget) Refresh() { 50 if widget.client.accessToken == nil { 51 metaData, err := readMetaDataFromDisk() 52 if err != nil || metaData.AccessToken == nil { 53 widget.Redraw(widget.authorizeWorkFlow) 54 return 55 } 56 widget.client.accessToken = metaData.AccessToken 57 } 58 59 state := Unread 60 if widget.archivedView { 61 state = Read 62 } 63 response, err := widget.client.GetLinks(state) 64 if err != nil { 65 widget.SetItemCount(0) 66 } 67 68 widget.items = orderItemResponseByKey(response) 69 widget.SetItemCount(len(widget.items)) 70 widget.Redraw(widget.content) 71 } 72 73 /* -------------------- Unexported Functions -------------------- */ 74 75 type pocketMetaData struct { 76 AccessToken *string 77 } 78 79 func writeMetaDataToDisk(metaData pocketMetaData) error { 80 81 fileData, err := yaml.Marshal(metaData) 82 if err != nil { 83 return fmt.Errorf("could not write token to disk %w", err) 84 } 85 86 wtfConfigDir, err := cfg.WtfConfigDir() 87 88 if err != nil { 89 return nil 90 } 91 92 filePath := fmt.Sprintf("%s/%s", wtfConfigDir, "pocket.data") 93 err = os.WriteFile(filePath, fileData, 0644) 94 95 return err 96 } 97 98 func readMetaDataFromDisk() (pocketMetaData, error) { 99 wtfConfigDir, err := cfg.WtfConfigDir() 100 var metaData pocketMetaData 101 if err != nil { 102 return metaData, err 103 } 104 filePath := fmt.Sprintf("%s/%s", wtfConfigDir, "pocket.data") 105 fileData, err := utils.ReadFileBytes(filePath) 106 107 if err != nil { 108 return metaData, err 109 } 110 111 err = yaml.Unmarshal(fileData, &metaData) 112 113 return metaData, err 114 115 } 116 117 /* 118 Authorization workflow is documented at https://getpocket.com/developer/docs/authentication 119 broken to 4 steps : 120 121 1- Obtain a platform consumer key from http://getpocket.com/developer/apps/new. 122 2- Obtain a request token 123 3- Redirect user to Pocket to continue authorization 124 4- Receive the callback from Pocket, this wont be used 125 5- Convert a request token into a Pocket access token 126 */ 127 func (widget *Widget) authorizeWorkFlow() (string, string, bool) { 128 title := widget.CommonSettings().Title 129 130 if widget.settings.requestKey == nil { 131 requestToken, err := widget.client.ObtainRequestToken() 132 133 if err != nil { 134 logger.Log(err.Error()) 135 return title, err.Error(), true 136 } 137 widget.settings.requestKey = &requestToken 138 redirectURL := widget.client.CreateAuthLink(requestToken) 139 content := fmt.Sprintf("Please click on %s to Authorize the app", redirectURL) 140 return title, content, true 141 } 142 143 if widget.settings.accessToken == nil { 144 accessToken, err := widget.client.GetAccessToken(*widget.settings.requestKey) 145 if err != nil { 146 logger.Log(err.Error()) 147 redirectURL := widget.client.CreateAuthLink(*widget.settings.requestKey) 148 content := fmt.Sprintf("Please click on %s to Authorize the app", redirectURL) 149 return title, content, true 150 } 151 content := "Authorized" 152 widget.settings.accessToken = &accessToken 153 154 metaData := pocketMetaData{ 155 AccessToken: &accessToken, 156 } 157 158 err = writeMetaDataToDisk(metaData) 159 if err != nil { 160 content = err.Error() 161 } 162 163 return title, content, true 164 } 165 166 content := "Authorized" 167 return title, content, true 168 169 } 170 171 func (widget *Widget) toggleView() { 172 widget.archivedView = !widget.archivedView 173 widget.Refresh() 174 } 175 176 func (widget *Widget) openLink() { 177 sel := widget.GetSelected() 178 if sel >= 0 && widget.items != nil && sel < len(widget.items) { 179 item := &widget.items[sel] 180 utils.OpenFile(item.GivenURL) 181 } 182 } 183 184 func (widget *Widget) toggleLink() { 185 sel := widget.GetSelected() 186 action := Archive 187 if widget.archivedView { 188 action = ReAdd 189 } 190 191 if sel >= 0 && widget.items != nil && sel < len(widget.items) { 192 item := &widget.items[sel] 193 _, err := widget.client.ModifyLink(action, item.ItemID) 194 if err != nil { 195 logger.Log(err.Error()) 196 } 197 } 198 199 widget.Refresh() 200 } 201 202 func (widget *Widget) formatItem(item Item, isSelected bool) string { 203 foreColor, backColor := widget.settings.Colors.RowTheme.EvenForeground, widget.settings.Colors.RowTheme.EvenBackground 204 text := item.ResolvedTitle 205 if isSelected { 206 foreColor = widget.settings.Colors.RowTheme.HighlightedForeground 207 backColor = widget.settings.Colors.RowTheme.HighlightedBackground 208 209 } 210 211 return fmt.Sprintf("[%s:%s]%s[white]", foreColor, backColor, tview.Escape(text)) 212 } 213 214 func (widget *Widget) content() (string, string, bool) { 215 title := widget.CommonSettings().Title 216 currentViewTitle := "Reading List" 217 if widget.archivedView { 218 currentViewTitle = "Archived list" 219 } 220 221 title = fmt.Sprintf("%s-%s", title, currentViewTitle) 222 content := "" 223 224 for i, v := range widget.items { 225 content += widget.formatItem(v, i == widget.Selected) + "\n" 226 } 227 228 return title, content, false 229 }