package main import ( "errors" "fmt" "net/http" "net/http/httputil" "net/url" "strconv" "strings" "time" ) func mainHandler(ds DataStore, w http.ResponseWriter, r *http.Request) { r.ParseForm() if _, ok := r.Form["hs"]; ok { protocol := r.FormValue("p") if protocol != "1.2.1" && protocol != "1.2" { fmt.Fprintln(w, "FAILED Protocol mismatch") return } timestamp := r.FormValue("t") ts, err := strconv.ParseInt(timestamp, 10, 0) if err != nil { fmt.Fprintln(w, "FAILED Invalid timestamp") return } delta := time.Now().Unix() - ts if delta > 30 || delta < -30 { fmt.Fprintln(w, "BADTIME") return } user := r.FormValue("u") auth := r.FormValue("a") password, err := ds.GetPassword(user) if (md5hex(password+timestamp) != auth) || err != nil { fmt.Fprintln(w, "BADAUTH") return } client := r.FormValue("c") s := NewSession(user, client, protocol) ds.PutSession(s) fmt.Fprintln(w, "OK") fmt.Fprintf(w, "%s\n", s.Key) fmt.Fprintln(w, "http://post.audioscrobbler.com:80/np") fmt.Fprintln(w, "http://post.audioscrobbler.com:80/scrobble") } else { fmt.Fprintf(w, "This is an endpoint, see here") } } func parseValues(values url.Values) map[int]url.Values { parts := make(map[int]url.Values) var field string for key, value := range values { splitted_key := strings.SplitN(key, "[", 2) if len(splitted_key) < 2 { continue } field = splitted_key[0] idx, err := strconv.Atoi(strings.TrimSuffix(splitted_key[1], "]")) if err != nil { continue } else { if _, ok := parts[idx]; !ok { parts[idx] = make(url.Values) } parts[idx][field] = value } } return parts } func parsePart1(values url.Values) (Scrobble, error) { var scrobble Scrobble scrobble.TrackName = NewCorrectable(values.Get("t")) scrobble.Artist = NewCorrectable(values.Get("a")) if time, err := strconv.Atoi(values.Get("i")); err != nil { return scrobble, errors.New("Could not parse timestamp") } else { scrobble.Time = time } scrobble.Album = NewCorrectable(values.Get("b")) scrobble.Mbid = values.Get("m") if tn, err := strconv.Atoi(values.Get("n")); err != nil { return scrobble, errors.New("Could not parse track number") } else { scrobble.TrackNumber = tn } if duration, err := strconv.Atoi(values.Get("l")); err != nil { return scrobble, errors.New("Could not parse duration") } else { scrobble.Duration = duration } chosen := values.Get("o") if chosen == "P" || chosen == "" { scrobble.Chosen = true } return scrobble, nil } func parsePart2(values url.Values) (Scrobble, error) { var scrobble Scrobble scrobble.TrackName = NewCorrectable(values.Get("track")) scrobble.Artist = NewCorrectable(values.Get("artist")) if time, err := strconv.Atoi(values.Get("timestamp")); err != nil { return scrobble, errors.New("Could not parse timestamp") } else { scrobble.Time = time } scrobble.Album = NewCorrectable(values.Get("album")) scrobble.Mbid = values.Get("mbid") if tn, ok := values["trackNumber"]; ok { if tn, err := strconv.Atoi(tn[0]); err != nil { return scrobble, errors.New("Could not parse track number") } else { scrobble.TrackNumber = tn } } if duration, err := strconv.Atoi(values.Get("duration")); err != nil { return scrobble, errors.New("Could not parse duration") } else { scrobble.Duration = duration } chosen := values.Get("chosenByUser") if chosen == "1" || chosen == "" { scrobble.Chosen = true } return scrobble, nil } func parseScrobbles(values url.Values, session *Session) ([]Scrobble, int) { scrobbles := make([]Scrobble, 0, 1) parts := parseValues(values) var parsePart func(url.Values) (Scrobble, error) if session.Protocol == "2.0" { parsePart = parsePart2 } else { parsePart = parsePart1 } ignored := 0 for i, c := 0, 0; i < 50 && c < len(parts); i++ { if part, ok := parts[i]; ok { c++ if scrobble, err := parsePart(part); err != nil { fmt.Printf("%v\n", err) ignored++ } else { scrobble.Session = session.Key scrobbles = append(scrobbles, scrobble) } } } return scrobbles, ignored } func scrobbleHandler(ds DataStore, w http.ResponseWriter, r *http.Request) { if session, err := ds.GetSession(r.FormValue("s")); err != nil { fmt.Fprintln(w, "BADSESSION") } else { scrobbles, _ := parseScrobbles(r.Form, session) fmt.Printf("%v\n", scrobbles) ds.PutScrobbles(scrobbles) fmt.Fprintln(w, "OK") } } func nowPlayingHandler(ds DataStore, w http.ResponseWriter, r *http.Request) { if _, err := ds.GetSession(r.FormValue("s")); err != nil { fmt.Fprintln(w, "BADSESSION") } else { fmt.Fprintln(w, "OK") } } type handler func(DataStore, http.ResponseWriter, *http.Request) func wrap(ds DataStore, fn handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { d, _ := httputil.DumpRequest(r, true) fmt.Printf("--------------------------\n%s\n", d) r.ParseForm() fn(ds, w, r) } } func main() { store := NewSqlStore() http.HandleFunc("/", wrap(store, handler(mainHandler))) http.HandleFunc("/np", wrap(store, nowPlayingHandler)) http.HandleFunc("/scrobble", wrap(store, scrobbleHandler)) http.HandleFunc("/2.0/", wrap(store, ApiHandler)) http.ListenAndServe(":3001", nil) }