yellowcab/spotify/spotify.go
2023-11-03 18:35:36 -05:00

215 lines
5.7 KiB
Go

package spotify
import (
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
"encoding/base64"
"encoding/json"
"git.gay/besties/yellowcab/db"
"git.gay/besties/yellowcab/scrape"
"github.com/joho/godotenv"
_ "github.com/mattn/go-sqlite3"
)
type spotify struct {
Name string
}
var (
SPOTIFY_ID string
SPOTIFY_SECRET string
bytes []byte
refresh_token string
err error
timestamp time.Time
)
func init() {
var err error = godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
// spotify loading
SPOTIFY_ID = url.PathEscape(os.Getenv("SPOTIFY_ID"))
SPOTIFY_SECRET = url.PathEscape(os.Getenv("SPOTIFY_SECRET"))
}
func sendAuthedRequest(url string, bearer string) (resp *http.Response, err error) {
req, err := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+bearer)
resp, err = http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("couldn't fulfill request to %s: %v", url, err)
}
return
}
func Refresh(refresh_token string) (refreshResponse *SpotifyRefreshResponse) {
form := url.Values{}
form.Add("grant_type", "refresh_token")
form.Add("refresh_token", refresh_token)
form.Add("client_id", SPOTIFY_ID)
bearer := "Basic " + base64.StdEncoding.EncodeToString([]byte(SPOTIFY_ID+":"+SPOTIFY_SECRET))
req, err := http.NewRequest("POST", "https://accounts.spotify.com/api/token", strings.NewReader(form.Encode()))
req.Header.Set("Authorization", bearer)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("couldn't get token: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("%s bad status code: %v", refresh_token, resp.StatusCode)
return
}
bytes, err = io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("couldn't get io readall: %v", err)
}
refreshResponse = &SpotifyRefreshResponse{}
err = json.Unmarshal(bytes, refreshResponse)
if err != nil {
log.Fatalf("err decoding this shit: %v", err)
}
return refreshResponse
}
func makeErrorResp(main string, sub string) (response *scrape.ScrobblerResponse) {
response = &scrape.ScrobblerResponse{
FromScrobbler: scrape.FromScrobbler{
Name: Spotify.Name,
},
Cover: nil,
Album: nil,
Artist: &scrape.Artist{
Name: sub,
},
Track: scrape.Track{
Name: main,
URL: nil,
StreamURL: nil,
},
EstTimestamp: "live",
TimestampMs: 0,
}
return
}
func (s *spotify) itemToTrack(item SpotifyItem, ts int64) *scrape.ScrobblerResponse {
return &scrape.ScrobblerResponse{
FromScrobbler: scrape.FromScrobbler{
Name: s.Name,
},
Cover: &item.Album.Images[0].URL,
Album: &scrape.Album{
URL: &item.Album.ExternalUrls.Spotify,
Name: item.Album.Name,
},
Artist: &scrape.Artist{
URL: &item.Artists[0].ExternalUrls.Spotify,
Name: item.Artists[0].Name,
},
Track: scrape.Track{
Name: item.Name,
URL: &item.ExternalUrls.Spotify,
StreamURL: &item.ExternalUrls.Spotify,
},
EstTimestamp: "live",
TimestampMs: int64(ts),
}
}
func (s *spotify) GetNowPlaying(username string) (response *scrape.ScrobblerResponse, err error) {
var refresh_token string
if db.HasRefreshToken(username) {
refresh_token, err = db.GetRefreshToken(username)
refresh_token = url.PathEscape(refresh_token)
if err != nil {
response = makeErrorResp("Database error", "Error getting token from database")
return
}
} else {
response = makeErrorResp("Missing user authentication", "Log in @ "+os.Getenv("BASE_URL")+"/spotifyauth")
return
}
refreshResponse := Refresh(refresh_token)
err = db.SetRefreshToken(username, refreshResponse.RefreshToken)
if err != nil {
response = makeErrorResp("Database error", "Error setting refresh token in database")
return
}
currentlyPlayingHTTPResp, err := sendAuthedRequest("https://api.spotify.com/v1/me/player/currently-playing", refreshResponse.AccessToken)
if err != nil {
response = makeErrorResp("Error getting currently playing", "Error getting currently playing")
return
}
defer currentlyPlayingHTTPResp.Body.Close()
var currentlyPlayingResponse *SpotifyPlayingResponse
if currentlyPlayingHTTPResp.StatusCode == http.StatusOK {
bytes, err = io.ReadAll(currentlyPlayingHTTPResp.Body)
currentlyPlayingResponse := &SpotifyPlayingResponse{}
err = json.Unmarshal(bytes, currentlyPlayingResponse)
if err != nil {
log.Fatalf("err unmarshalling json: %v", err)
}
response = s.itemToTrack(*currentlyPlayingResponse.Item, int64(currentlyPlayingResponse.Timestamp))
return
}
if currentlyPlayingResponse == nil || !currentlyPlayingResponse.IsPlaying {
// not playing anything, check history
var lastPlayedHTTPResp *http.Response
lastPlayedHTTPResp, err = sendAuthedRequest("https://api.spotify.com/v1/me/player/recently-played?limit=1", refreshResponse.AccessToken)
if err != nil {
response = makeErrorResp("Error getting last played", "Error getting last played")
return
}
defer lastPlayedHTTPResp.Body.Close()
if lastPlayedHTTPResp.StatusCode != http.StatusOK {
response = makeErrorResp("Not currently listening to anything", "No listening history")
return
}
lastPlayedResponse := &SpotifyLastPlayed{}
var bytes []byte
bytes, err = io.ReadAll(lastPlayedHTTPResp.Body)
err = json.Unmarshal(bytes, lastPlayedResponse)
if err != nil {
log.Fatalf("err unmarshalling json: %v", err)
}
timestamp, err = time.Parse(time.RFC3339, lastPlayedResponse.Items[0].PlayedAt)
if err != nil {
log.Fatalf("err parsing time: %v", err)
}
response = s.itemToTrack(lastPlayedResponse.Items[0].Track, timestamp.Unix())
return
}
response = makeErrorResp("No hits - Not listening", "No listening history")
return
}
var Spotify = spotify{
Name: "Spotify",
}