From 25e8579fa0f6e39be1b2855a9a08660eb5f205e0 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Thu, 28 Nov 2024 16:27:46 +0100 Subject: [PATCH] feat(song): lyrics --- config/development.toml | 1 + ...1128115057_alter_song_table_add_lyrics.sql | 23 +++ db/queries/song.sql | 25 +--- go.mod | 52 ++++--- go.sum | 11 +- internal/pkg/db/dto/song.go | 28 ++++ internal/pkg/db/sqlc/models.go | 4 + internal/pkg/db/sqlc/song.sql.go | 133 ++++-------------- internal/pkg/song/api.go | 56 ++++++++ internal/pkg/song/song.go | 11 +- 10 files changed, 181 insertions(+), 163 deletions(-) create mode 100644 db/migrations/20241128115057_alter_song_table_add_lyrics.sql diff --git a/config/development.toml b/config/development.toml index 055ad23..e8ddddd 100644 --- a/config/development.toml +++ b/config/development.toml @@ -5,6 +5,7 @@ port = 3000 [song] spotify_api = "https://api.spotify.com/v1" spotify_account = "https://accounts.spotify.com/api/token" +lrclib_api = "https://lrclib.net/api" [tap] api = "https://tap.zeus.gent" diff --git a/db/migrations/20241128115057_alter_song_table_add_lyrics.sql b/db/migrations/20241128115057_alter_song_table_add_lyrics.sql new file mode 100644 index 0000000..7219ea7 --- /dev/null +++ b/db/migrations/20241128115057_alter_song_table_add_lyrics.sql @@ -0,0 +1,23 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE song +ADD COLUMN album TEXT NOT NULL; + +ALTER TABLE song +ADD COLUMN lyrics_type TEXT; + +ALTER TABLE song +ADD COLUMN lyrics TEXT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE song +DROP COLUMN isrc_id; + +ALTER TABLE song +DROP COLUMN lyrics; + +ALTER TABLE song +DROP COLUMN common_id; +-- +goose StatementEnd diff --git a/db/queries/song.sql b/db/queries/song.sql index 595d7fd..66707ba 100644 --- a/db/queries/song.sql +++ b/db/queries/song.sql @@ -1,17 +1,8 @@ -- CRUD --- name: GetAllSongs :many -SELECT * -FROM song; - --- name: GetSongByID :one -SELECT * -FROM song -WHERE id = ?; - -- name: CreateSong :one -INSERT INTO song (title, spotify_id, duration_ms) -VALUES (?, ?, ?) +INSERT INTO song (title, album, spotify_id, duration_ms, lyrics_type, lyrics) +VALUES (?, ?, ?, ?, ?, ?) RETURNING *; -- name: CreateSongHistory :one @@ -39,16 +30,6 @@ INSERT INTO song_artist_genre (artist_id, genre_id) VALUES (?, ?) RETURNING *; --- name: UpdateSong :one -UPDATE song -SET title = ?, spotify_id = ?, duration_ms = ? -WHERE id = ? -RETURNING *; - --- name: DeleteSong :execrows -DELETE FROM song -WHERE id = ?; - -- Other @@ -79,7 +60,7 @@ FROM song_artist WHERE name = ?; -- name: GetLastSongFull :many -SELECT s.title AS song_title, s.spotify_id, s.duration_ms, a.name AS artist_name, g.genre AS genre +SELECT s.title AS song_title, s.spotify_id, s.album, s.duration_ms, s.lyrics_type, s.lyrics, a.name AS artist_name, g.genre AS genre FROM song_history sh JOIN song s ON sh.song_id = s.id LEFT JOIN song_artist_song sa ON s.id = sa.song_id diff --git a/go.mod b/go.mod index 0a6f16f..174bb6a 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,19 @@ module github.com/zeusWPI/scc go 1.23.1 require ( + github.com/NimbleMarkets/ntcharts v0.1.2 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/lipgloss v1.0.0 github.com/disintegration/imaging v1.6.2 + github.com/go-playground/validator/v10 v10.22.1 + github.com/gocolly/colly v1.2.0 + github.com/gofiber/contrib/fiberzap v1.0.2 + github.com/gofiber/fiber/v2 v2.52.5 github.com/joho/godotenv v1.5.1 github.com/lucasb-eyer/go-colorful v1.2.0 + github.com/mattn/go-sqlite3 v1.14.24 github.com/spf13/viper v1.19.0 + go.uber.org/zap v1.27.0 ) @@ -23,6 +30,7 @@ require ( github.com/charmbracelet/bubbles v0.18.0 // indirect github.com/charmbracelet/x/ansi v0.4.2 // indirect github.com/containerd/console v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -30,58 +38,46 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.5.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/kennygrant/sanitize v1.2.4 // indirect github.com/klauspost/compress v1.17.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lrstanley/bubblezone v0.0.0-20240125042004-b7bafc493195 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect - github.com/temoto/robotstxt v1.1.2 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.51.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/image v0.11.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/term v0.24.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect -) - -require ( - github.com/NimbleMarkets/ntcharts v0.1.2 - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-playground/validator/v10 v10.22.1 - github.com/gocolly/colly v1.2.0 - github.com/gofiber/contrib/fiberzap v1.0.2 - github.com/gofiber/fiber/v2 v2.52.5 - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-sqlite3 v1.14.24 - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/temoto/robotstxt v1.1.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect golang.org/x/text v0.18.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9499d22..e8f6556 100644 --- a/go.sum +++ b/go.sum @@ -75,11 +75,8 @@ github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8Nz github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= @@ -172,9 +169,8 @@ golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -214,7 +210,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -230,8 +225,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/pkg/db/dto/song.go b/internal/pkg/db/dto/song.go index 4476d3c..3caaeaa 100644 --- a/internal/pkg/db/dto/song.go +++ b/internal/pkg/db/dto/song.go @@ -1,6 +1,8 @@ package dto import ( + "database/sql" + "github.com/zeusWPI/scc/internal/pkg/db/sqlc" ) @@ -8,8 +10,11 @@ import ( type Song struct { ID int64 `json:"id"` Title string `json:"title"` + Album string `json:"album"` SpotifyID string `json:"spotify_id" validate:"required"` DurationMS int64 `json:"duration_ms"` + LyricsType string `json:"lyrics_type"` // Either 'synced' or 'plain' + Lyrics string `json:"lyrics"` Artists []SongArtist `json:"artists"` } @@ -31,20 +36,43 @@ type SongGenre struct { // SongDTO converts a sqlc.Song to a Song func SongDTO(song sqlc.Song) *Song { + var lyricsType string + if song.LyricsType.Valid { + lyricsType = song.Lyrics.String + } + var lyrics string + if song.Lyrics.Valid { + lyrics = song.Lyrics.String + } + return &Song{ ID: song.ID, Title: song.Title, + Album: song.Album, SpotifyID: song.SpotifyID, DurationMS: song.DurationMs, + LyricsType: lyricsType, + Lyrics: lyrics, } } // CreateSongParams converts a Song DTO to a sqlc CreateSongParams object func (s *Song) CreateSongParams() *sqlc.CreateSongParams { + lyricsType := sql.NullString{String: s.LyricsType, Valid: false} + if s.LyricsType != "" { + lyricsType.Valid = true + } + lyrics := sql.NullString{String: s.Lyrics, Valid: false} + if s.Lyrics != "" { + lyrics.Valid = true + } return &sqlc.CreateSongParams{ Title: s.Title, + Album: s.Album, SpotifyID: s.SpotifyID, DurationMs: s.DurationMS, + LyricsType: lyricsType, + Lyrics: lyrics, } } diff --git a/internal/pkg/db/sqlc/models.go b/internal/pkg/db/sqlc/models.go index 5bff769..2aaef06 100644 --- a/internal/pkg/db/sqlc/models.go +++ b/internal/pkg/db/sqlc/models.go @@ -5,6 +5,7 @@ package sqlc import ( + "database/sql" "time" ) @@ -49,6 +50,9 @@ type Song struct { Title string SpotifyID string DurationMs int64 + Album string + LyricsType sql.NullString + Lyrics sql.NullString } type SongArtist struct { diff --git a/internal/pkg/db/sqlc/song.sql.go b/internal/pkg/db/sqlc/song.sql.go index 8ba42fe..05bdbbc 100644 --- a/internal/pkg/db/sqlc/song.sql.go +++ b/internal/pkg/db/sqlc/song.sql.go @@ -11,25 +11,40 @@ import ( ) const createSong = `-- name: CreateSong :one -INSERT INTO song (title, spotify_id, duration_ms) -VALUES (?, ?, ?) -RETURNING id, title, spotify_id, duration_ms + +INSERT INTO song (title, album, spotify_id, duration_ms, lyrics_type, lyrics) +VALUES (?, ?, ?, ?, ?, ?) +RETURNING id, title, spotify_id, duration_ms, album, lyrics_type, lyrics ` type CreateSongParams struct { Title string + Album string SpotifyID string DurationMs int64 + LyricsType sql.NullString + Lyrics sql.NullString } +// CRUD func (q *Queries) CreateSong(ctx context.Context, arg CreateSongParams) (Song, error) { - row := q.db.QueryRowContext(ctx, createSong, arg.Title, arg.SpotifyID, arg.DurationMs) + row := q.db.QueryRowContext(ctx, createSong, + arg.Title, + arg.Album, + arg.SpotifyID, + arg.DurationMs, + arg.LyricsType, + arg.Lyrics, + ) var i Song err := row.Scan( &i.ID, &i.Title, &i.SpotifyID, &i.DurationMs, + &i.Album, + &i.LyricsType, + &i.Lyrics, ) return i, err } @@ -127,56 +142,8 @@ func (q *Queries) CreateSongHistory(ctx context.Context, songID int64) (SongHist return i, err } -const deleteSong = `-- name: DeleteSong :execrows -DELETE FROM song -WHERE id = ? -` - -func (q *Queries) DeleteSong(ctx context.Context, id int64) (int64, error) { - result, err := q.db.ExecContext(ctx, deleteSong, id) - if err != nil { - return 0, err - } - return result.RowsAffected() -} - -const getAllSongs = `-- name: GetAllSongs :many - -SELECT id, title, spotify_id, duration_ms -FROM song -` - -// CRUD -func (q *Queries) GetAllSongs(ctx context.Context) ([]Song, error) { - rows, err := q.db.QueryContext(ctx, getAllSongs) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Song - for rows.Next() { - var i Song - if err := rows.Scan( - &i.ID, - &i.Title, - &i.SpotifyID, - &i.DurationMs, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - const getLastSongFull = `-- name: GetLastSongFull :many -SELECT s.title AS song_title, s.spotify_id, s.duration_ms, a.name AS artist_name, g.genre AS genre +SELECT s.title AS song_title, s.spotify_id, s.album, s.duration_ms, s.lyrics_type, s.lyrics, a.name AS artist_name, g.genre AS genre FROM song_history sh JOIN song s ON sh.song_id = s.id LEFT JOIN song_artist_song sa ON s.id = sa.song_id @@ -190,7 +157,10 @@ ORDER BY a.name, g.genre type GetLastSongFullRow struct { SongTitle string SpotifyID string + Album string DurationMs int64 + LyricsType sql.NullString + Lyrics sql.NullString ArtistName sql.NullString Genre sql.NullString } @@ -207,7 +177,10 @@ func (q *Queries) GetLastSongFull(ctx context.Context) ([]GetLastSongFullRow, er if err := rows.Scan( &i.SongTitle, &i.SpotifyID, + &i.Album, &i.DurationMs, + &i.LyricsType, + &i.Lyrics, &i.ArtistName, &i.Genre, ); err != nil { @@ -276,27 +249,9 @@ func (q *Queries) GetSongArtistBySpotifyID(ctx context.Context, spotifyID string return i, err } -const getSongByID = `-- name: GetSongByID :one -SELECT id, title, spotify_id, duration_ms -FROM song -WHERE id = ? -` - -func (q *Queries) GetSongByID(ctx context.Context, id int64) (Song, error) { - row := q.db.QueryRowContext(ctx, getSongByID, id) - var i Song - err := row.Scan( - &i.ID, - &i.Title, - &i.SpotifyID, - &i.DurationMs, - ) - return i, err -} - const getSongBySpotifyID = `-- name: GetSongBySpotifyID :one -SELECT id, title, spotify_id, duration_ms +SELECT id, title, spotify_id, duration_ms, album, lyrics_type, lyrics FROM song WHERE spotify_id = ? ` @@ -310,6 +265,9 @@ func (q *Queries) GetSongBySpotifyID(ctx context.Context, spotifyID string) (Son &i.Title, &i.SpotifyID, &i.DurationMs, + &i.Album, + &i.LyricsType, + &i.Lyrics, ) return i, err } @@ -326,34 +284,3 @@ func (q *Queries) GetSongGenreByName(ctx context.Context, genre string) (SongGen err := row.Scan(&i.ID, &i.Genre) return i, err } - -const updateSong = `-- name: UpdateSong :one -UPDATE song -SET title = ?, spotify_id = ?, duration_ms = ? -WHERE id = ? -RETURNING id, title, spotify_id, duration_ms -` - -type UpdateSongParams struct { - Title string - SpotifyID string - DurationMs int64 - ID int64 -} - -func (q *Queries) UpdateSong(ctx context.Context, arg UpdateSongParams) (Song, error) { - row := q.db.QueryRowContext(ctx, updateSong, - arg.Title, - arg.SpotifyID, - arg.DurationMs, - arg.ID, - ) - var i Song - err := row.Scan( - &i.ID, - &i.Title, - &i.SpotifyID, - &i.DurationMs, - ) - return i, err -} diff --git a/internal/pkg/song/api.go b/internal/pkg/song/api.go index b58d31c..c0d0869 100644 --- a/internal/pkg/song/api.go +++ b/internal/pkg/song/api.go @@ -3,6 +3,7 @@ package song import ( "errors" "fmt" + "net/url" "github.com/gofiber/fiber/v2" "github.com/zeusWPI/scc/internal/pkg/db/dto" @@ -17,8 +18,13 @@ type trackArtist struct { Name string `json:"name"` } +type trackAlbum struct { + Name string `json:"name"` +} + type trackResponse struct { Name string `json:"name"` + Album trackAlbum `json:"album"` Artists []trackArtist `json:"artists"` DurationMS int64 `json:"duration_ms"` } @@ -39,6 +45,7 @@ func (s *Song) getTrack(track *dto.Song) error { } track.Title = res.Name + track.Album = res.Album.Name track.DurationMS = res.DurationMS for _, a := range res.Artists { @@ -85,3 +92,52 @@ func (s *Song) getArtist(artist *dto.SongArtist) error { return nil } + +type lyricsResponse struct { + PlainLyrics string `json:"plainLyrics"` + SyncedLyrics string `json:"SyncedLyrics"` +} + +func (s *Song) getLyrics(track *dto.Song) error { + // Get most popular artist + if len(track.Artists) == 0 { + return fmt.Errorf("Song: No artists for track: %v", track) + } + artist := track.Artists[0] + for _, a := range track.Artists { + if a.Followers > artist.Followers { + artist = a + } + } + + // Construct url + params := url.Values{} + params.Set("artist_name", artist.Name) + params.Set("track_name", track.Title) + params.Set("album_name", track.Album) + params.Set("duration", fmt.Sprintf("%d", track.DurationMS/1000)) + + req := fiber.Get(fmt.Sprintf("%s/get?%s", config.GetDefaultString("song.lrclib_api", "https://lrclib.net/api"), params.Encode())) + + res := new(lyricsResponse) + status, _, errs := req.Struct(res) + if len(errs) > 0 { + return errors.Join(append([]error{errors.New("Song: Lyrics request failed")}, errs...)...) + } + if status != fiber.StatusOK { + return fmt.Errorf("Song: Lyrics request wrong status code %d", status) + } + if (res == &lyricsResponse{}) { + return errors.New("Song: Lyrics request returned empty struct") + } + + if res.SyncedLyrics != "" { + track.LyricsType = "synced" + track.Lyrics = res.SyncedLyrics + } else { + track.LyricsType = "plain" + track.Lyrics = res.PlainLyrics + } + + return nil +} diff --git a/internal/pkg/song/song.go b/internal/pkg/song/song.go index 34eec0d..3abdbb7 100644 --- a/internal/pkg/song/song.go +++ b/internal/pkg/song/song.go @@ -36,6 +36,8 @@ func New(db *db.DB) (*Song, error) { // Track gets information about the current track and stores it in the database func (s *Song) Track(track *dto.Song) error { + var errs []error + if s.ClientID == "" || s.ClientSecret == "" { return errors.New("Song: Spotify client id or secret not set") } @@ -78,15 +80,20 @@ func (s *Song) Track(track *dto.Song) error { return err } + // Get lyrics + if err = s.getLyrics(track); err != nil { + errs = append(errs, err) + } + // Store track in DB trackDB, err = s.db.Queries.CreateSong(context.Background(), *track.CreateSongParams()) if err != nil { - return err + errs = append(errs, err) + return errors.Join(errs...) } track.ID = trackDB.ID // Handle artists - var errs []error for i, artist := range track.Artists { a, err := s.db.Queries.GetSongArtistBySpotifyID(context.Background(), artist.SpotifyID) if err != nil && err != sql.ErrNoRows {