From ea54772c03b6db69c5fc2d3e8713c044c39cdd32 Mon Sep 17 00:00:00 2001 From: Yonah Date: Wed, 17 Sep 2025 07:49:06 +0900 Subject: [PATCH] song: struct for /api/song/%d This also includes tests against a sample response from the https://monster-siren.hypergryph.com/api/song/048794 endpoint. Signed-off-by: Yonah --- song.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++ song_test.go | 23 +++++++++++++++++++ songs.go | 8 ------- testdata/song.json | 1 + 4 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 song.go create mode 100644 song_test.go create mode 100644 testdata/song.json diff --git a/song.go b/song.go new file mode 100644 index 0000000..5190fc5 --- /dev/null +++ b/song.go @@ -0,0 +1,55 @@ +package monstersirenfetch + +import ( + "bytes" + "encoding/json" +) + +// 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"` +} + +// IsFull returns whether the metadata held by [Song] is considered full (originating from a [SongResponse]). +func (s *Song) IsFull() bool { return s.SourceURL != "" } + +// 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 := json.NewEncoder(buf) + e.SetEscapeHTML(false) + + 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}) +} diff --git a/song_test.go b/song_test.go new file mode 100644 index 0000000..5b13451 --- /dev/null +++ b/song_test.go @@ -0,0 +1,23 @@ +package monstersirenfetch_test + +import ( + _ "embed" + "testing" + + . "git.gensokyo.uk/yonah/monstersirenfetch" +) + +//go:embed testdata/song.json +var songJSON []byte + +func TestSong(t *testing.T) { + checkJSONRoundTrip(t, SongResponse{Data: Song{ + CID: 48794, + Name: "Warm and Small Light", + AlbumCID: 6660, + SourceURL: "https://res01.hycdn.cn/04ce5de54bb52eb85008644d541d40fa/68CA0442/siren/audio/20240709/a7f650238eaefc9c30a9627d7f78d819.wav", + LyricURL: "https://web.hycdn.cn/siren/lyric/20240709/4a10c70629b68a187fdbef4a27bd32d8.lrc", + MvURL: "", MvCoverURL: "", + Artists: []string{"塞壬唱片-MSR"}, + }}, songJSON) +} diff --git a/songs.go b/songs.go index b4f540a..2c41555 100644 --- a/songs.go +++ b/songs.go @@ -8,11 +8,3 @@ type SongsData = struct { List []Song `json:"list"` Autoplay string `json:"autoplay"` } - -// Song represents the metadata of a song. -type Song struct { - CID StringInt `json:"cid"` - Name string `json:"name"` - AlbumCID StringInt `json:"albumCid"` - Artists []string `json:"artists"` -} diff --git a/testdata/song.json b/testdata/song.json new file mode 100644 index 0000000..6ff980c --- /dev/null +++ b/testdata/song.json @@ -0,0 +1 @@ +{"code":0,"msg":"","data":{"cid":"048794","name":"Warm and Small Light","albumCid":"6660","sourceUrl":"https://res01.hycdn.cn/04ce5de54bb52eb85008644d541d40fa/68CA0442/siren/audio/20240709/a7f650238eaefc9c30a9627d7f78d819.wav","lyricUrl":"https://web.hycdn.cn/siren/lyric/20240709/4a10c70629b68a187fdbef4a27bd32d8.lrc","mvUrl":null,"mvCoverUrl":null,"artists":["塞壬唱片-MSR"]}} \ No newline at end of file