diff --git a/plugins/lyrics.go b/plugins/lyrics.go index 09a29df..f40a9f7 100644 --- a/plugins/lyrics.go +++ b/plugins/lyrics.go @@ -31,14 +31,18 @@ type Lyrics struct { type geniusSearchResponse struct { Response struct { - Hits []struct { - Result geniusSong `json:"result"` - } `json:"hits"` + Hits []geniusSearchHit `json:"hits"` } `json:"response"` } +type geniusSearchHit struct { + // Genius returns the hit type beside the nested result object. Keep this + // at the hit level so song results are not mistaken for empty results. + Type string `json:"type"` + Result geniusSong `json:"result"` +} + type geniusSong struct { - Type string `json:"type"` Title string `json:"title"` FullTitle string `json:"full_title"` ArtistNames string `json:"artist_names"` @@ -230,7 +234,7 @@ func lookupGeniusSong(ctx context.Context, query, token string) (geniusSong, err return geniusSong{}, err } for _, hit := range payload.Response.Hits { - if !strings.EqualFold(strings.TrimSpace(hit.Result.Type), "song") { + if !strings.EqualFold(strings.TrimSpace(hit.Type), "song") { continue } if !validGeniusSongURL(hit.Result.URL) { diff --git a/plugins/lyrics_test.go b/plugins/lyrics_test.go index 6cc5e1f..a2057c8 100644 --- a/plugins/lyrics_test.go +++ b/plugins/lyrics_test.go @@ -68,8 +68,8 @@ func TestLookupGeniusSongUsesTokenAndSelectsSong(t *testing.T) { t.Fatalf("Authorization = %q", got) } return newPluginResponse(http.StatusOK, `{"response":{"hits":[ - {"result":{"type":"artist","title":"Artist","url":"https://genius.com/artists/artist"}}, - {"result":{"type":"song","title":"Song","artist_names":"Artist","url":"https://genius.com/Artist-song-lyrics"}} + {"type":"artist","result":{"title":"Artist","url":"https://genius.com/artists/artist"}}, + {"type":"song","result":{"title":"Song","artist_names":"Artist","url":"https://genius.com/Artist-song-lyrics"}} ]}}`), nil })} @@ -87,8 +87,8 @@ func TestLookupGeniusSongRejectsInvalidResults(t *testing.T) { t.Cleanup(func() { apiHTTPClient = old }) apiHTTPClient = &http.Client{Transport: newPluginRoundTripper(func(*http.Request) (*http.Response, error) { return newPluginResponse(http.StatusOK, `{"response":{"hits":[ - {"result":{"type":"artist","title":"Artist","url":"https://genius.com/artists/artist"}}, - {"result":{"type":"song","title":"Bad host","url":"https://evil.example/song"}} + {"type":"artist","result":{"title":"Artist","url":"https://genius.com/artists/artist"}}, + {"type":"song","result":{"title":"Bad host","url":"https://evil.example/song"}} ]}}`), nil })} if _, err := lookupGeniusSong(t.Context(), "song", "token"); !errors.Is(err, errGeniusNotFound) { @@ -127,7 +127,7 @@ func TestLyricsHandleReturnsOneBoundedLine(t *testing.T) { t.Cleanup(func() { apiHTTPClient = old }) t.Setenv("BOT_GENIUS_ACCESS_TOKEN", "test-token") apiHTTPClient = &http.Client{Transport: newPluginRoundTripper(func(*http.Request) (*http.Response, error) { - return newPluginResponse(http.StatusOK, `{"response":{"hits":[{"result":{"type":"song","title":"Song\ufe0f","artist_names":"Artist\u200d","url":"https://genius.com/Artist-song-lyrics"}}]}}`), nil + return newPluginResponse(http.StatusOK, `{"response":{"hits":[{"type":"song","result":{"title":"Song\ufe0f","artist_names":"Artist\u200d","url":"https://genius.com/Artist-song-lyrics"}}]}}`), nil })} sent := make(chan bot.Outgoing, 2)