This is still quite easy to stub, and the extra information is very useful to the caller. Signed-off-by: Yonah <contrib@gensokyo.uk>
144 lines
3.7 KiB
Go
144 lines
3.7 KiB
Go
package monstersirenfetch
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"slices"
|
|
)
|
|
|
|
// SongResponse is the response of /api/song/%d.
|
|
type SongResponse Response[Song]
|
|
|
|
// Song holds the metadata of a song.
|
|
type Song struct {
|
|
CID StringInt `json:"cid"`
|
|
Name string `json:"name"`
|
|
AlbumCID StringInt `json:"albumCid"`
|
|
SourceURL string `json:"sourceUrl,omitempty"`
|
|
LyricURL string `json:"lyricUrl,omitempty"`
|
|
MvURL string `json:"mvUrl,omitempty"`
|
|
MvCoverURL string `json:"mvCoverUrl,omitempty"`
|
|
Artists []string `json:"artists"`
|
|
}
|
|
|
|
const (
|
|
// SongVariantCurrent copies the current variant as-is.
|
|
SongVariantCurrent = iota
|
|
// SongVariantFull leaves all fields intact in the copy.
|
|
// This variant is returned by /api/song/%d.
|
|
SongVariantFull
|
|
// SongVariantBase zeroes [Song.SourceURL], [Song.LyricURL], [Song.MvURL], [Song.MvCoverURL].
|
|
// This variant is included in /api/songs.
|
|
SongVariantBase
|
|
)
|
|
|
|
// IsFull returns whether [Song] is considered to be its [SongVariantFull] variant.
|
|
func (s *Song) IsFull() bool { return s.SourceURL != "" }
|
|
|
|
// Copy makes a copy of [Song].
|
|
// For a [Song] where the IsFull method returns true, Copy zeroes fields to convert the copy into other variants.
|
|
// For [Song] where IsFull returns false, any variant other than [SongVariantCurrent] is undefined.
|
|
func (s *Song) Copy(v *Song, variant int) bool {
|
|
if v == nil || s == nil || v == s {
|
|
return false
|
|
}
|
|
|
|
if variant == SongVariantCurrent {
|
|
*v = *s
|
|
return true
|
|
}
|
|
if !s.IsFull() {
|
|
return false
|
|
}
|
|
|
|
switch variant {
|
|
case SongVariantFull:
|
|
*v = *s
|
|
|
|
case SongVariantBase:
|
|
*v = *s
|
|
v.SourceURL = ""
|
|
v.LyricURL = ""
|
|
v.MvURL = ""
|
|
v.MvCoverURL = ""
|
|
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Enrich populates the remaining fields of a non-full [Song].
|
|
func (s *Song) Enrich(ctx context.Context, n Net) error {
|
|
if s == nil || s.IsFull() {
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
var v SongResponse
|
|
u := APIPrefix + "/song/" + s.CID.String()
|
|
if resp, err := n.Get(ctx, u); err != nil {
|
|
return err
|
|
} else if resp.StatusCode != http.StatusOK {
|
|
return &ResponseStatusError{u, resp.StatusCode, resp.Body.Close()}
|
|
} else {
|
|
if err = json.NewDecoder(resp.Body).Decode(&v); err != nil {
|
|
return errors.Join(resp.Body.Close(), err)
|
|
}
|
|
if err = resp.Body.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// these should be unreachable unless the server malfunctions
|
|
if s.CID != v.Data.CID {
|
|
return &InconsistentEnrichError[StringInt]{"cid", s.CID, v.Data.CID}
|
|
}
|
|
if s.Name != v.Data.Name {
|
|
return &InconsistentEnrichError[string]{"name", s.Name, v.Data.Name}
|
|
}
|
|
if s.AlbumCID != v.Data.AlbumCID {
|
|
return &InconsistentEnrichError[StringInt]{"albumCid", s.AlbumCID, v.Data.AlbumCID}
|
|
}
|
|
if !slices.Equal(s.Artists, v.Data.Artists) {
|
|
return &InconsistentEnrichError[[]string]{"artists", s.Artists, v.Data.Artists}
|
|
}
|
|
|
|
*s = v.Data
|
|
return nil
|
|
}
|
|
|
|
// songDirect is [Song] without its MarshalJSON method.
|
|
type songDirect Song
|
|
|
|
// songNullable is [Song] with corresponding nullable string fields.
|
|
type songNullable struct {
|
|
CID StringInt `json:"cid"`
|
|
Name string `json:"name"`
|
|
AlbumCID StringInt `json:"albumCid"`
|
|
SourceURL string `json:"sourceUrl"`
|
|
LyricURL NullableString `json:"lyricUrl"`
|
|
MvURL NullableString `json:"mvUrl"`
|
|
MvCoverURL NullableString `json:"mvCoverUrl"`
|
|
Artists []string `json:"artists"`
|
|
}
|
|
|
|
func (s *Song) MarshalJSON() (data []byte, err error) {
|
|
buf := new(bytes.Buffer)
|
|
e := NewEncoder(buf)
|
|
|
|
if !s.IsFull() {
|
|
err = e.Encode((*songDirect)(s))
|
|
data = buf.Bytes()
|
|
return
|
|
}
|
|
|
|
return json.Marshal(&songNullable{s.CID, s.Name, s.AlbumCID, s.SourceURL,
|
|
NullableString(s.LyricURL), NullableString(s.MvURL), NullableString(s.MvCoverURL),
|
|
s.Artists})
|
|
}
|