mirror of
https://github.com/riwiwa/muzi.git
synced 2026-02-28 11:56:57 -08:00
finish lastfm endpoint and fix clearing spotify now playing status
This commit is contained in:
@@ -23,6 +23,15 @@ func NewLastFMHandler() *LastFMHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *LastFMHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *LastFMHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "GET" {
|
||||||
|
if r.URL.Query().Get("hs") == "true" {
|
||||||
|
h.handleHandshake(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if r.Method != "POST" {
|
if r.Method != "POST" {
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
@@ -34,9 +43,12 @@ func (h *LastFMHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
method := r.FormValue("method")
|
method := r.PostForm.Get("method")
|
||||||
apiKey := r.FormValue("api_key")
|
apiKey := r.PostForm.Get("api_key")
|
||||||
|
sk := r.PostForm.Get("s")
|
||||||
|
track := r.PostForm.Get("t")
|
||||||
|
|
||||||
|
if method != "" {
|
||||||
switch method {
|
switch method {
|
||||||
case "auth.gettoken":
|
case "auth.gettoken":
|
||||||
h.handleGetToken(w, apiKey)
|
h.handleGetToken(w, apiKey)
|
||||||
@@ -49,16 +61,26 @@ func (h *LastFMHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
default:
|
default:
|
||||||
h.respond(w, "failed", 400, fmt.Sprintf("Invalid method: %s", method))
|
h.respond(w, "failed", 400, fmt.Sprintf("Invalid method: %s", method))
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sk != "" {
|
||||||
|
if r.PostForm.Get("a[0]") != "" && (r.PostForm.Get("t[0]") != "" || r.PostForm.Get("i[0]") != "") {
|
||||||
|
h.handleScrobble(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if track != "" {
|
||||||
|
h.handleNowPlaying(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.respond(w, "failed", 400, "Missing required parameters")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *LastFMHandler) respond(w http.ResponseWriter, status string, code int, message string) {
|
func (h *LastFMHandler) respond(w http.ResponseWriter, status string, code int, message string) {
|
||||||
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
fmt.Fprintf(w, `<?xml version="1.0" encoding="utf-8"?>
|
w.Write([]byte(fmt.Sprintf("FAILED %s", message)))
|
||||||
<lfm status="%s">
|
|
||||||
<error code="%d">
|
|
||||||
<message>%s</message>
|
|
||||||
</error>
|
|
||||||
</lfm>`, status, code, message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *LastFMHandler) respondOK(w http.ResponseWriter, content string) {
|
func (h *LastFMHandler) respondOK(w http.ResponseWriter, content string) {
|
||||||
@@ -66,6 +88,40 @@ func (h *LastFMHandler) respondOK(w http.ResponseWriter, content string) {
|
|||||||
w.Write([]byte(content))
|
w.Write([]byte(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *LastFMHandler) handleHandshake(w http.ResponseWriter, r *http.Request) {
|
||||||
|
username := r.URL.Query().Get("u")
|
||||||
|
token := r.URL.Query().Get("t")
|
||||||
|
authToken := r.URL.Query().Get("a")
|
||||||
|
|
||||||
|
if username == "" || token == "" || authToken == "" {
|
||||||
|
w.Write([]byte("BADAUTH"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userId, err := GetUserByUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
w.Write([]byte("BADAUTH"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKey, err := GenerateSessionKey()
|
||||||
|
if err != nil {
|
||||||
|
w.Write([]byte("FAILED Could not generate session"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Pool.Exec(context.Background(),
|
||||||
|
`UPDATE users SET api_secret = $1 WHERE pk = $2`,
|
||||||
|
sessionKey, userId)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error updating session key: %v\n", err)
|
||||||
|
w.Write([]byte("FAILED Database error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(fmt.Sprintf("OK\n%s\nhttp://127.0.0.1:1234/2.0/\nhttp://127.0.0.1:1234/2.0/\n", sessionKey)))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *LastFMHandler) handleGetToken(w http.ResponseWriter, apiKey string) {
|
func (h *LastFMHandler) handleGetToken(w http.ResponseWriter, apiKey string) {
|
||||||
userId, _, err := GetUserByAPIKey(apiKey)
|
userId, _, err := GetUserByAPIKey(apiKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -121,7 +177,7 @@ func (h *LastFMHandler) handleGetSession(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *LastFMHandler) handleNowPlaying(w http.ResponseWriter, r *http.Request) {
|
func (h *LastFMHandler) handleNowPlaying(w http.ResponseWriter, r *http.Request) {
|
||||||
sessionKey := r.FormValue("sk")
|
sessionKey := r.PostForm.Get("s")
|
||||||
if sessionKey == "" {
|
if sessionKey == "" {
|
||||||
h.respond(w, "failed", 9, "Invalid session")
|
h.respond(w, "failed", 9, "Invalid session")
|
||||||
return
|
return
|
||||||
@@ -133,11 +189,16 @@ func (h *LastFMHandler) handleNowPlaying(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
artist := r.FormValue("artist")
|
artist := r.PostForm.Get("a")
|
||||||
track := r.FormValue("track")
|
track := r.PostForm.Get("t")
|
||||||
album := r.FormValue("album")
|
album := r.PostForm.Get("b")
|
||||||
|
|
||||||
duration := r.FormValue("duration")
|
if track == "" {
|
||||||
|
h.respondOK(w, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := r.PostForm.Get("l")
|
||||||
msPlayed := 0
|
msPlayed := 0
|
||||||
if duration != "" {
|
if duration != "" {
|
||||||
if d, err := strconv.Atoi(duration); err == nil {
|
if d, err := strconv.Atoi(duration); err == nil {
|
||||||
@@ -145,7 +206,6 @@ func (h *LastFMHandler) handleNowPlaying(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if track != "" {
|
|
||||||
UpdateNowPlaying(NowPlaying{
|
UpdateNowPlaying(NowPlaying{
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
SongName: track,
|
SongName: track,
|
||||||
@@ -155,14 +215,12 @@ func (h *LastFMHandler) handleNowPlaying(w http.ResponseWriter, r *http.Request)
|
|||||||
Platform: "lastfm_api",
|
Platform: "lastfm_api",
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
h.respondOK(w, `<?xml version="1.0" encoding="utf-8"?>
|
h.respondOK(w, "OK")
|
||||||
<lfm status="ok"></lfm>`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *LastFMHandler) handleScrobble(w http.ResponseWriter, r *http.Request) {
|
func (h *LastFMHandler) handleScrobble(w http.ResponseWriter, r *http.Request) {
|
||||||
sessionKey := r.FormValue("sk")
|
sessionKey := r.PostForm.Get("s")
|
||||||
if sessionKey == "" {
|
if sessionKey == "" {
|
||||||
h.respond(w, "failed", 9, "Invalid session")
|
h.respond(w, "failed", 9, "Invalid session")
|
||||||
return
|
return
|
||||||
@@ -174,7 +232,7 @@ func (h *LastFMHandler) handleScrobble(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scrobbles := h.parseScrobbles(r.Form, userId)
|
scrobbles := h.parseScrobbles(r.PostForm, userId)
|
||||||
if len(scrobbles) == 0 {
|
if len(scrobbles) == 0 {
|
||||||
h.respond(w, "failed", 1, "No scrobbles to submit")
|
h.respond(w, "failed", 1, "No scrobbles to submit")
|
||||||
return
|
return
|
||||||
@@ -194,10 +252,7 @@ func (h *LastFMHandler) handleScrobble(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ClearNowPlaying(userId)
|
ClearNowPlaying(userId)
|
||||||
|
|
||||||
h.respondOK(w, fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
|
h.respondOK(w, fmt.Sprintf("OK\n%d\n%d\n", accepted, ignored))
|
||||||
<lfm status="ok">
|
|
||||||
<scrobbles accepted="%d" ignored="%d"></scrobbles>
|
|
||||||
</lfm>`, accepted, ignored))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *LastFMHandler) parseScrobbles(form url.Values, userId int) []Scrobble {
|
func (h *LastFMHandler) parseScrobbles(form url.Values, userId int) []Scrobble {
|
||||||
@@ -207,15 +262,15 @@ func (h *LastFMHandler) parseScrobbles(form url.Values, userId int) []Scrobble {
|
|||||||
var artist, track, album, timestampStr string
|
var artist, track, album, timestampStr string
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
artist = form.Get("artist")
|
artist = form.Get("a[0]")
|
||||||
track = form.Get("track")
|
track = form.Get("t[0]")
|
||||||
album = form.Get("album")
|
album = form.Get("b[0]")
|
||||||
timestampStr = form.Get("timestamp")
|
timestampStr = form.Get("i[0]")
|
||||||
} else {
|
} else {
|
||||||
artist = form.Get(fmt.Sprintf("artist[%d]", i-1))
|
artist = form.Get(fmt.Sprintf("a[%d]", i))
|
||||||
track = form.Get(fmt.Sprintf("track[%d]", i-1))
|
track = form.Get(fmt.Sprintf("t[%d]", i))
|
||||||
album = form.Get(fmt.Sprintf("album[%d]", i-1))
|
album = form.Get(fmt.Sprintf("b[%d]", i))
|
||||||
timestampStr = form.Get(fmt.Sprintf("timestamp[%d]", i-1))
|
timestampStr = form.Get(fmt.Sprintf("i[%d]", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
if artist == "" || track == "" || timestampStr == "" {
|
if artist == "" || track == "" || timestampStr == "" {
|
||||||
@@ -227,7 +282,7 @@ func (h *LastFMHandler) parseScrobbles(form url.Values, userId int) []Scrobble {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
duration := form.Get(fmt.Sprintf("duration[%d]", i-1))
|
duration := form.Get(fmt.Sprintf("l[%d]", i))
|
||||||
msPlayed := 0
|
msPlayed := 0
|
||||||
if duration != "" {
|
if duration != "" {
|
||||||
if d, err := strconv.Atoi(duration); err == nil {
|
if d, err := strconv.Atoi(duration); err == nil {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type NowPlaying struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
var CurrentNowPlaying = make(map[int]NowPlaying)
|
var CurrentNowPlaying = make(map[int]map[string]NowPlaying)
|
||||||
|
|
||||||
func GenerateAPIKey() (string, error) {
|
func GenerateAPIKey() (string, error) {
|
||||||
bytes := make([]byte, 16)
|
bytes := make([]byte, 16)
|
||||||
@@ -80,6 +80,20 @@ func GetUserByAPIKey(apiKey string) (int, string, error) {
|
|||||||
return userId, username, nil
|
return userId, username, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserByUsername(username string) (int, error) {
|
||||||
|
if username == "" {
|
||||||
|
return 0, fmt.Errorf("empty username")
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId int
|
||||||
|
err := db.Pool.QueryRow(context.Background(),
|
||||||
|
"SELECT pk FROM users WHERE username = $1", username).Scan(&userId)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return userId, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserBySessionKey(sessionKey string) (int, string, error) {
|
func GetUserBySessionKey(sessionKey string) (int, string, error) {
|
||||||
if sessionKey == "" {
|
if sessionKey == "" {
|
||||||
return 0, "", fmt.Errorf("empty session key")
|
return 0, "", fmt.Errorf("empty session key")
|
||||||
@@ -167,18 +181,38 @@ func checkDuplicate(userId int, artist, songName string, timestamp time.Time) (b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateNowPlaying(np NowPlaying) {
|
func UpdateNowPlaying(np NowPlaying) {
|
||||||
CurrentNowPlaying[np.UserId] = np
|
if CurrentNowPlaying[np.UserId] == nil {
|
||||||
|
CurrentNowPlaying[np.UserId] = make(map[string]NowPlaying)
|
||||||
|
}
|
||||||
|
CurrentNowPlaying[np.UserId][np.Platform] = np
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNowPlaying(userId int) (NowPlaying, bool) {
|
func GetNowPlaying(userId int) (NowPlaying, bool) {
|
||||||
np, ok := CurrentNowPlaying[userId]
|
platforms := CurrentNowPlaying[userId]
|
||||||
return np, ok
|
if platforms == nil {
|
||||||
|
return NowPlaying{}, false
|
||||||
|
}
|
||||||
|
np, ok := platforms["lastfm_api"]
|
||||||
|
if ok && np.SongName != "" {
|
||||||
|
return np, true
|
||||||
|
}
|
||||||
|
np, ok = platforms["spotify"]
|
||||||
|
if ok && np.SongName != "" {
|
||||||
|
return np, true
|
||||||
|
}
|
||||||
|
return NowPlaying{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearNowPlaying(userId int) {
|
func ClearNowPlaying(userId int) {
|
||||||
delete(CurrentNowPlaying, userId)
|
delete(CurrentNowPlaying, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ClearNowPlayingPlatform(userId int, platform string) {
|
||||||
|
if CurrentNowPlaying[userId] != nil {
|
||||||
|
delete(CurrentNowPlaying[userId], platform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserSpotifyCredentials(userId int) (clientId, clientSecret, accessToken, refreshToken string, expiresAt time.Time, err error) {
|
func GetUserSpotifyCredentials(userId int) (clientId, clientSecret, accessToken, refreshToken string, expiresAt time.Time, err error) {
|
||||||
var clientIdPg, clientSecretPg, accessTokenPg, refreshTokenPg pgtype.Text
|
var clientIdPg, clientSecretPg, accessTokenPg, refreshTokenPg pgtype.Text
|
||||||
var expiresAtPg pgtype.Timestamptz
|
var expiresAtPg pgtype.Timestamptz
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ func checkCurrentlyPlaying(userId int, accessToken string) error {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode == 204 {
|
if resp.StatusCode == 204 {
|
||||||
ClearNowPlaying(userId)
|
ClearNowPlayingPlatform(userId, "spotify")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ func checkCurrentlyPlaying(userId int, accessToken string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !playing.IsPlaying || playing.Item.Name == "" {
|
if !playing.IsPlaying || playing.Item.Name == "" {
|
||||||
ClearNowPlaying(userId)
|
ClearNowPlayingPlatform(userId, "spotify")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
web/web.go
11
web/web.go
@@ -16,9 +16,15 @@ import (
|
|||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const serverAddr = "127.0.0.1:1234"
|
||||||
|
|
||||||
// 50 MiB
|
// 50 MiB
|
||||||
const maxHeaderSize int64 = 50 * 1024 * 1024
|
const maxHeaderSize int64 = 50 * 1024 * 1024
|
||||||
|
|
||||||
|
func serverAddrStr() string {
|
||||||
|
return serverAddr
|
||||||
|
}
|
||||||
|
|
||||||
// Holds all the parsed HTML templates
|
// Holds all the parsed HTML templates
|
||||||
var templates *template.Template
|
var templates *template.Template
|
||||||
|
|
||||||
@@ -68,7 +74,7 @@ func rootHandler() http.HandlerFunc {
|
|||||||
|
|
||||||
// Serves all pages at the specified address.
|
// Serves all pages at the specified address.
|
||||||
func Start() {
|
func Start() {
|
||||||
addr := ":1234"
|
addr := serverAddr
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Use(middleware.Logger)
|
r.Use(middleware.Logger)
|
||||||
r.Handle("/files/*", http.StripPrefix("/files", http.FileServer(http.Dir("./static"))))
|
r.Handle("/files/*", http.StripPrefix("/files", http.FileServer(http.Dir("./static"))))
|
||||||
@@ -84,7 +90,8 @@ func Start() {
|
|||||||
r.Get("/import/lastfm/progress", importLastFMProgressHandler)
|
r.Get("/import/lastfm/progress", importLastFMProgressHandler)
|
||||||
r.Get("/import/spotify/progress", importSpotifyProgressHandler)
|
r.Get("/import/spotify/progress", importSpotifyProgressHandler)
|
||||||
|
|
||||||
r.Post("/2.0/", http.HandlerFunc(scrobble.NewLastFMHandler().ServeHTTP))
|
r.Handle("/2.0", scrobble.NewLastFMHandler())
|
||||||
|
r.Handle("/2.0/", scrobble.NewLastFMHandler())
|
||||||
r.Post("/1/submit-listens", http.HandlerFunc(scrobble.NewListenbrainzHandler().ServeHTTP))
|
r.Post("/1/submit-listens", http.HandlerFunc(scrobble.NewListenbrainzHandler().ServeHTTP))
|
||||||
r.Route("/scrobble/spotify", func(r chi.Router) {
|
r.Route("/scrobble/spotify", func(r chi.Router) {
|
||||||
r.Get("/authorize", http.HandlerFunc(scrobble.NewSpotifyHandler().ServeHTTP))
|
r.Get("/authorize", http.HandlerFunc(scrobble.NewSpotifyHandler().ServeHTTP))
|
||||||
|
|||||||
Reference in New Issue
Block a user