package main import ( "database/sql" "database/sql/driver" "encoding/json" "encoding/xml" "errors" "fmt" "log" "net/http" "strings" "time" ) type Api interface { AuthGetToken(*http.Request) (ApiResponse, error) AuthGetSession(*http.Request) (ApiResponse, error) AuthGetMobileSession(*http.Request) (ApiResponse, error) //TrackScrobble(*http.Request) (ApiResponse, error) UpdateNowPlaying(*http.Request) (ApiResponse, error) } type ApiResponse interface { } type SuspendScrobbles Scrobbles func (s Scrobbles) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { Tag SuspendScrobbles `json:"scrobbles"` }{Tag: SuspendScrobbles(s)}) } type Name struct { XMLName xml.Name } type Token struct { XMLName xml.Name `xml:"token" json:"-"` Val string `xml:",innerxml" json:"token"` Created time.Time `xml:"-" json:"-"` UserId sql.NullInt64 `xml:"-" json:"-"` } type LFMResponse struct { XMLName xml.Name `xml:"lfm" json:"-"` Status string `xml:"status,attr" json:"-"` Response ApiResponse } type Attrs struct { Accepted int `xml:"accepted,attr" json:"accepted"` Ignored int `xml:"ignored,attr" json:"ignored"` } type Scrobbles struct { XMLName xml.Name `xml:"scrobbles" json:"-"` Scrobbles []Scrobble `xml:"scrobble" json:"scrobble"` Attrs `json:"@attr"` } type Correctable struct { Name string `xml:",chardata" json:"#text"` Corrected int `xml:"corrected,attr" json:"corrected"` } func (field Correctable) Value() (driver.Value, error) { return field.Name, nil } func NewCorrectable(name string) Correctable { return Correctable{ Name: name, } } func (field Correctable) String() string { return field.Name } func (n Name) getName() string { return n.XMLName.Local } func (store *SqlStore) AuthGetToken(r *http.Request) (ApiResponse, error) { if token, err := store.NewToken(); err != nil { return nil, err } else { return Token{Val: token}, nil } } func (store *SqlStore) AuthGetMobileSession(r *http.Request) (ApiResponse, error) { return nil, nil } func (store *SqlStore) VerifySig(r *http.Request) error { key := r.FormValue("api_key") token := r.FormValue("token") sig := r.FormValue("api_sig") method := r.FormValue("method") if c, err := store.GetClient(key); err != nil { return errors.New("Invalid API key") } else { to_hash := fmt.Sprintf("api_key%smethod%stoken%s%s", key, method, token, c.Secret) if md5hex(to_hash) == sig { return nil } else { return errors.New("Invalid method signature supplied") } } } func (store *SqlStore) AuthGetSession(r *http.Request) (ApiResponse, error) { if err := store.VerifySig(r); err == nil { if token, err := store.GetToken(r.FormValue("token")); err != nil { // FIXME: error 4 return nil, errors.New("Invalid authentication token supplied") } else { if time.Since(token.Created) > 5*time.Minute { // FIXME: error 15 return nil, errors.New("This token has expired") } else { if token.UserId.Valid { user := &User{Id: int(token.UserId.Int64)} if err := store.GetUser(user); err != nil { return nil, err } else { session := &Session{ User: user.Name, Key: randomToken(16), Client: r.FormValue("api_key"), UserId: user.Id, Protocol: "2.0", } store.PutSession(session) return session, nil } } else { //FIXME: error 14 return nil, errors.New("This token has not been authorized") } } } } else { return nil, err } } func (app *App) TrackScrobble(r *http.Request) (ApiResponse, error) { if session, err := app.GetSession(r.FormValue("sk")); err != nil { return nil, err } else { scrobbles, ignored := parseScrobbles(r.Form, session) for i, s := range scrobbles { app.GetScrobbleSong(&s) scrobbles[i] = s } if err := app.PutScrobbles(scrobbles); err != nil { return nil, err } else { return Scrobbles{Scrobbles: scrobbles, Attrs: Attrs{len(scrobbles), ignored}}, nil } } } func (store *SqlStore) UpdateNowPlaying(r *http.Request) (ApiResponse, error) { if session, err := store.GetSession(r.FormValue("sk")); err != nil { return nil, err } else { fmt.Println("TODO") fmt.Printf("%v", session) return nil, nil } } func (app *App) ApiHandler(w http.ResponseWriter, r *http.Request) { method := r.FormValue("method") var response ApiResponse var err error switch strings.ToLower(method) { case "auth.gettoken": response, err = app.AuthGetToken(r) case "auth.getsession": response, err = app.AuthGetSession(r) case "auth.getmobilesession": response, err = app.AuthGetMobileSession(r) case "track.scrobble": response, err = app.TrackScrobble(r) case "update.nowplaying": response, err = app.UpdateNowPlaying(r) } if err != nil { log.Println("%v", err) } var text []byte switch r.FormValue("format") { case "json": text, _ = json.Marshal(response) default: xmlresponse := LFMResponse{Status: "ok", Response: response} text, _ = xml.Marshal(xmlresponse) fmt.Fprint(w, xml.Header) } fmt.Printf("%s\n", text) w.Write(text) }