github.com/wtfutil/wtf@v0.43.0/modules/spotifyweb/widget.go (about) 1 package spotifyweb 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 "os" 8 "time" 9 10 "github.com/rivo/tview" 11 "github.com/wtfutil/wtf/utils" 12 "github.com/wtfutil/wtf/view" 13 "github.com/zmb3/spotify" 14 ) 15 16 var ( 17 auth spotify.Authenticator 18 tempClientChan = make(chan *spotify.Client) 19 state = "wtfSpotifyWebStateString" 20 authURL string 21 callbackPort string 22 redirectURI string 23 ) 24 25 // Info is the struct that contains all the information the Spotify player displays to the user 26 type Info struct { 27 Artists string 28 Title string 29 Album string 30 TrackNumber int 31 Status string 32 } 33 34 // Widget is the struct used by all WTF widgets to transfer to the main widget controller 35 type Widget struct { 36 view.TextWidget 37 38 Info 39 40 client *spotify.Client 41 clientChan chan *spotify.Client 42 playerState *spotify.PlayerState 43 settings *Settings 44 } 45 46 func authHandler(w http.ResponseWriter, r *http.Request) { 47 tok, err := auth.Token(state, r) 48 if err != nil { 49 http.Error(w, "Couldn't get token", http.StatusForbidden) 50 } 51 if st := r.FormValue("state"); st != state { 52 http.NotFound(w, r) 53 } 54 // use the token to get an authenticated client 55 client := auth.NewClient(tok) 56 _, err = fmt.Fprintf(w, "Login Completed!") 57 if err != nil { 58 return 59 } 60 tempClientChan <- &client 61 } 62 63 // NewWidget creates a new widget for WTF 64 func NewWidget(tviewApp *tview.Application, redrawChan chan bool, pages *tview.Pages, settings *Settings) *Widget { 65 redirectURI = "http://localhost:" + settings.callbackPort + "/callback" 66 67 auth = spotify.NewAuthenticator(redirectURI, spotify.ScopeUserReadCurrentlyPlaying, spotify.ScopeUserReadPlaybackState, spotify.ScopeUserModifyPlaybackState) 68 auth.SetAuthInfo(settings.clientID, settings.secretKey) 69 authURL = auth.AuthURL(state) 70 71 var client *spotify.Client 72 var playerState *spotify.PlayerState 73 74 widget := Widget{ 75 TextWidget: view.NewTextWidget(tviewApp, redrawChan, pages, settings.Common), 76 77 Info: Info{}, 78 79 client: client, 80 clientChan: tempClientChan, 81 playerState: playerState, 82 settings: settings, 83 } 84 85 http.HandleFunc("/callback", authHandler) 86 go func() { 87 err := http.ListenAndServe(":"+callbackPort, nil) 88 if err != nil { 89 return 90 } 91 }() 92 93 go func() { 94 // wait for auth to complete 95 client = <-tempClientChan 96 97 // use the client to make calls that require authorization 98 _, err := client.CurrentUser() 99 if err != nil { 100 panic(err) 101 } 102 103 playerState, err = client.PlayerState() 104 if err != nil { 105 fmt.Println(err) 106 os.Exit(1) 107 } 108 109 widget.client = client 110 widget.playerState = playerState 111 widget.Refresh() 112 }() 113 114 // While I wish I could find the reason this doesn't work, I can't. 115 // 116 // Normally, this should open the URL to the browser, however it opens the Explorer window in Windows. 117 // This mostly likely has to do with the fact that the URL includes some very special characters that no terminal likes. 118 // The only solution would be to include quotes in the command, which is why I do here, but it doesn't work. 119 // 120 // If inconvenient, I'll remove this option and save the URL in a file or some other method. 121 utils.OpenFile(`"` + authURL + `"`) 122 123 widget.settings.RefreshInterval = 5 * time.Second 124 125 widget.initializeKeyboardControls() 126 127 widget.View.SetWrap(true) 128 widget.View.SetWordWrap(true) 129 130 return &widget 131 } 132 133 func (w *Widget) refreshSpotifyInfos() error { 134 if w.client == nil || w.playerState == nil { 135 return errors.New("authentication failed! Please log in to Spotify by visiting the following page in your browser: " + authURL) 136 } 137 var err error 138 w.playerState, err = w.client.PlayerState() 139 if err != nil { 140 return errors.New("extracting player state failed! Please refresh or restart WTF") 141 } 142 w.Info.Album = fmt.Sprint(w.playerState.CurrentlyPlaying.Item.Album.Name) 143 artists := "" 144 for _, artist := range w.playerState.CurrentlyPlaying.Item.Artists { 145 artists += artist.Name + ", " 146 } 147 artists = artists[:len(artists)-2] 148 w.Info.Artists = artists 149 w.Info.Title = fmt.Sprint(w.playerState.CurrentlyPlaying.Item.Name) 150 w.Info.TrackNumber = w.playerState.CurrentlyPlaying.Item.TrackNumber 151 if w.playerState.CurrentlyPlaying.Playing { 152 w.Info.Status = "Playing" 153 } else { 154 w.Info.Status = "Paused" 155 } 156 return nil 157 } 158 159 // Refresh refreshes the current view of the widget 160 func (w *Widget) Refresh() { 161 w.Redraw(w.createOutput) 162 } 163 164 func (w *Widget) createOutput() (string, string, bool) { 165 var output string 166 167 err := w.refreshSpotifyInfos() 168 if err != nil { 169 output = err.Error() 170 } else { 171 output += utils.CenterText(fmt.Sprintf("[green]Now %v [white]\n", w.Info.Status), w.CommonSettings().Width) 172 output += utils.CenterText(fmt.Sprintf("[green]Title:[white] %v\n", w.Info.Title), w.CommonSettings().Width) 173 output += utils.CenterText(fmt.Sprintf("[green]Artist:[white] %v\n", w.Info.Artists), w.CommonSettings().Width) 174 output += utils.CenterText(fmt.Sprintf("[green]Album:[white] %v\n", w.Info.Album), w.CommonSettings().Width) 175 if w.playerState.ShuffleState { 176 output += utils.CenterText("[green]Shuffle:[white] on\n", w.CommonSettings().Width) 177 } else { 178 output += utils.CenterText("[green]Shuffle:[white] off\n", w.CommonSettings().Width) 179 } 180 } 181 return w.CommonSettings().Title, output, true 182 }