Files
plexbrainz/main.go

168 lines
4.1 KiB
Go
Raw Permalink Normal View History

2025-10-19 15:47:39 +10:30
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
var lbToken string
var plexUsername string
var plexLibsStr string
var listen = ":9102"
func main() {
lbToken = os.Getenv("PB_LISTENBRAINZ_USER_TOKEN")
plexUsername = os.Getenv("PB_PLEX_USERNAME")
plexLibsStr = os.Getenv("PB_PLEX_LIBRARIES")
if lbToken == "" {
log.Fatal("you must set PB_LISTENBRAINZ_USER_TOKEN - see https://listenbrainz.org/settings/")
}
if plexUsername == "" {
log.Fatal("you must set PB_PLEX_USERNAME to the user who's listens will be recorded")
}
if plexLibsStr == "" {
log.Fatal("you must set PB_PLEX_LIBRARIES to a comma separated list of plex libraries which will be scrobbled")
}
plexLibs := strings.Split(plexLibsStr, ",")
http.HandleFunc("POST /plex", func(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(1 * 1024 * 1024)
if err != nil {
log.Printf("error parsing multipart form: %s", err.Error())
return
}
pl := r.FormValue("payload")
event := webhookEvent{}
err = json.Unmarshal([]byte(pl), &event)
if err != nil {
log.Printf("error parsing JSON: %s", err.Error())
w.WriteHeader(400)
w.Write([]byte("could not parse your JSON"))
return
}
// log.Printf("event: %s", event.Event)
// log.Printf("account name: %s", event.Account.Title)
// log.Printf("title: %s", event.Metadata.Title)
// log.Printf("album: %s", event.Metadata.ParentTitle)
// log.Printf("artist: %s", event.Metadata.GrandparentTitle)
// log.Printf("library: %s", event.Metadata.LibrarySectionTitle)
// 2025/10/19 09:21:31 event: media.play
// 2025/10/19 09:21:31 account name: username
// 2025/10/19 09:21:31 title: Face to Face (Cosmo Vitelli remix)
// 2025/10/19 09:21:31 album: Daft Club
// 2025/10/19 09:21:31 artist: Daft Punk
// 2025/10/19 09:21:31 library: Music
if event.Account.Title != plexUsername {
log.Printf("not scrobbling for user '%s'", plexUsername)
w.WriteHeader(200)
w.Write([]byte("ok"))
return
}
if event.Event == "media.scrobble" {
found := false
for i := range plexLibs {
if event.Metadata.LibrarySectionTitle == plexLibs[i] {
found = true
break
}
}
if !found {
log.Printf("not scrobbling for library '%s'", event.Metadata.LibrarySectionTitle)
log.Printf("does not match one of: '%s'", plexLibsStr)
w.WriteHeader(200)
w.Write([]byte("ok"))
return
}
err = lbSubmit(event.Metadata.Title, event.Metadata.GrandparentTitle, event.Metadata.ParentTitle)
if err != nil {
log.Printf("error submitting: %s", err.Error())
w.WriteHeader(400)
w.Write([]byte("could not parse your JSON"))
return
} else {
log.Printf("scrobbled play of %s - %s", event.Metadata.GrandparentTitle, event.Metadata.Title)
w.WriteHeader(200)
w.Write([]byte("ok"))
return
}
} else {
log.Printf("non scrobble event: %s", event.Event)
w.WriteHeader(200)
w.Write([]byte("ok"))
return
}
})
log.Printf("starting web server on %s", listen)
log.Fatal(http.ListenAndServe(listen, nil))
}
func lbSubmit(title, artist, album string) error {
c := http.Client{}
c.Timeout = time.Second * 10
url := "https://api.listenbrainz.org/1/submit-listens"
b := bytes.Buffer{}
pl := lbListen{
ListenType: "single",
Payload: []lbListenPayload{{
ListenedAt: time.Now().Unix(),
TrackMetadata: lbTrackMetadata{
ArtistName: artist,
TrackName: title,
ReleaseName: album,
},
}},
}
err := json.NewEncoder(&b).Encode(pl)
if err != nil {
return err
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b.Bytes()))
if err != nil {
return err
}
req.Header.Set("Authorization", fmt.Sprintf("Token %s", lbToken))
req.Header.Set("Content-Type", "application/json")
res, err := c.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
errB := bytes.Buffer{}
io.Copy(&errB, res.Body)
return fmt.Errorf("got non-200 response: %d - body: %s", res.StatusCode, errB.String())
}
// happy path
2025-10-20 11:17:23 +10:30
resB := bytes.Buffer{}
io.Copy(&resB, res.Body)
log.Printf("listenbrainz OK - response: `%s`", resB.String())
2025-10-19 15:47:39 +10:30
return nil
}