Compare commits

..

No commits in common. "main" and "v0.4.1" have entirely different histories.
main ... v0.4.1

25 changed files with 545 additions and 439 deletions

68
go.mod
View file

@ -1,31 +1,30 @@
module go.uploadedlobster.com/scotty module go.uploadedlobster.com/scotty
go 1.23.0 go 1.22.0
toolchain go1.24.2 toolchain go1.22.2
require ( require (
github.com/Xuanwo/go-locale v1.1.3 github.com/Xuanwo/go-locale v1.1.2
github.com/agnivade/levenshtein v1.2.1 github.com/agnivade/levenshtein v1.1.1
github.com/cli/browser v1.3.0 github.com/cli/browser v1.3.0
github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238
github.com/fatih/color v1.18.0 github.com/fatih/color v1.17.0
github.com/glebarez/sqlite v1.11.0 github.com/glebarez/sqlite v1.11.0
github.com/go-resty/resty/v2 v2.16.5 github.com/go-resty/resty/v2 v2.15.0
github.com/jarcoal/httpmock v1.3.1 github.com/jarcoal/httpmock v1.3.1
github.com/manifoldco/promptui v0.9.0 github.com/manifoldco/promptui v0.9.0
github.com/pelletier/go-toml/v2 v2.2.4 github.com/pelletier/go-toml/v2 v2.2.3
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0
github.com/spf13/cast v1.7.1 github.com/spf13/cast v1.7.0
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.20.1 github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.9.0
github.com/vbauerster/mpb/v8 v8.9.3 github.com/vbauerster/mpb/v8 v8.8.3
go.uploadedlobster.com/mbtypes v0.4.0 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 golang.org/x/oauth2 v0.23.0
golang.org/x/oauth2 v0.29.0 golang.org/x/text v0.18.0
golang.org/x/text v0.24.0 gorm.io/datatypes v1.2.2
gorm.io/datatypes v1.2.5
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
) )
@ -36,38 +35,37 @@ require (
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-colorable v0.1.14 // 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-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.29.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/sys v0.25.0 // indirect
golang.org/x/sync v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/tools v0.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.7 // indirect gorm.io/driver/mysql v1.5.7 // indirect
modernc.org/libc v1.62.1 // indirect modernc.org/libc v1.60.1 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.9.1 // indirect modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.37.0 // indirect modernc.org/sqlite v1.33.1 // indirect
) )
tool golang.org/x/text/cmd/gotext

312
go.sum
View file

@ -2,12 +2,14 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/Xuanwo/go-locale v1.1.3 h1:EWZZJJt5rqPHHbqPRH1zFCn5D7xHjjebODctA4aUO3A= github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg=
github.com/Xuanwo/go-locale v1.1.3/go.mod h1:REn+F/c+AtGSWYACBSYZgl23AP+0lfQC+SEFPN+hj30= github.com/Xuanwo/go-locale v1.1.0/go.mod h1:UKrHoZB3FPIk9wIG2/tVSobnHgNnceGSH3Y8DY5cASs=
github.com/Xuanwo/go-locale v1.1.2 h1:6H+olvrQcyVOZ+GAC2rXu4armacTT4ZrFCA0mB24XVo=
github.com/Xuanwo/go-locale v1.1.2/go.mod h1:1JBER4QV7Ji39GJ4AvVlfvqmTUqopzxQxdg2mXYOw94=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -21,80 +23,105 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5 h1:RuuxidatioSKGOiBzL1mTY4X22DQD8weEbS3iRLHnAg=
github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo=
github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 h1:uejyepOdHISrJTw7P84Y7yEC0FMyv1q3KNDRxWsviKw= github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238 h1:uejyepOdHISrJTw7P84Y7yEC0FMyv1q3KNDRxWsviKw=
github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
github.com/go-resty/resty/v2 v2.15.0 h1:clPQLZ2x9h4yGY81IzpMPnty+xoGyFaDg0XMkCsHf90=
github.com/go-resty/resty/v2 v2.15.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@ -105,94 +132,193 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs=
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0/go.mod h1:n3nudMl178cEvD44PaopxH9jhJaQzthSxUzLO5iKMy4= github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0/go.mod h1:n3nudMl178cEvD44PaopxH9jhJaQzthSxUzLO5iKMy4=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY=
github.com/smartystreets/goconvey v1.6.7/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/vbauerster/mpb/v8 v8.9.3 h1:PnMeF+sMvYv9u23l6DO6Q3+Mdj408mjLRXIzmUmU2Z8= github.com/vbauerster/mpb/v8 v8.7.3 h1:n/mKPBav4FFWp5fH4U0lPpXfiOmCEgl5Yx/NM3tKJA0=
github.com/vbauerster/mpb/v8 v8.9.3/go.mod h1:hxS8Hz4C6ijnppDSIX6LjG8FYJSoPo9iIOcE53Zik0c= github.com/vbauerster/mpb/v8 v8.7.3/go.mod h1:9nFlNpDGVoTmQ4QvNjSLtwLmAFjwmq0XaAF26toHGNM=
github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ=
github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uploadedlobster.com/mbtypes v0.4.0 h1:D5asCgHsRWufj4Yn5u0IuH2J9z1UuYImYkYIp1Z1Q7s= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
go.uploadedlobster.com/mbtypes v0.4.0/go.mod h1:Bu1K1Hl77QTAE2Z7QKiW/JAp9KqYWQebkRRfG02dlZM= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 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/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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I= gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4= gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
gorm.io/datatypes v1.2.2 h1:sdn7ZmG4l7JWtMDUb3L98f2Ym7CO5F8mZLlrQJMfF9g=
gorm.io/datatypes v1.2.2/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI=
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s=
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY=
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4=
modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View file

@ -41,7 +41,7 @@ func (b *DumpBackend) ImportListens(export models.ListensResult, importResult mo
importResult.UpdateTimestamp(listen.ListenedAt) importResult.UpdateTimestamp(listen.ListenedAt)
importResult.ImportCount += 1 importResult.ImportCount += 1
msg := fmt.Sprintf("🎶 %v: \"%v\" by %v (%v)", msg := fmt.Sprintf("🎶 %v: \"%v\" by %v (%v)",
listen.ListenedAt, listen.TrackName, listen.ArtistName(), listen.RecordingMBID) listen.ListenedAt, listen.TrackName, listen.ArtistName(), listen.RecordingMbid)
importResult.Log(models.Info, msg) importResult.Log(models.Info, msg)
progress <- models.Progress{}.FromImportResult(importResult) progress <- models.Progress{}.FromImportResult(importResult)
} }
@ -54,7 +54,7 @@ func (b *DumpBackend) ImportLoves(export models.LovesResult, importResult models
importResult.UpdateTimestamp(love.Created) importResult.UpdateTimestamp(love.Created)
importResult.ImportCount += 1 importResult.ImportCount += 1
msg := fmt.Sprintf("❤️ %v: \"%v\" by %v (%v)", msg := fmt.Sprintf("❤️ %v: \"%v\" by %v (%v)",
love.Created, love.TrackName, love.ArtistName(), love.RecordingMBID) love.Created, love.TrackName, love.ArtistName(), love.RecordingMbid)
importResult.Log(models.Info, msg) importResult.Log(models.Info, msg)
progress <- models.Progress{}.FromImportResult(importResult) progress <- models.Progress{}.FromImportResult(importResult)
} }

View file

@ -20,7 +20,6 @@ import (
"sort" "sort"
"time" "time"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/i18n"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
@ -176,7 +175,7 @@ func (f FavoriteTrack) AsLove() models.Love {
track := f.Track.AsTrack() track := f.Track.AsTrack()
love := models.Love{ love := models.Love{
UserName: f.User.UserName, UserName: f.User.UserName,
RecordingMBID: track.RecordingMBID, RecordingMbid: track.RecordingMbid,
Track: track, Track: track,
} }
@ -189,15 +188,16 @@ func (f FavoriteTrack) AsLove() models.Love {
} }
func (t Track) AsTrack() models.Track { func (t Track) AsTrack() models.Track {
recordingMbid := models.MBID(t.RecordingMbid)
track := models.Track{ track := models.Track{
TrackName: t.Title, TrackName: t.Title,
ReleaseName: t.Album.Title, ReleaseName: t.Album.Title,
ArtistNames: []string{t.Artist.Name}, ArtistNames: []string{t.Artist.Name},
TrackNumber: t.Position, TrackNumber: t.Position,
DiscNumber: t.DiscNumber, DiscNumber: t.DiscNumber,
RecordingMBID: t.RecordingMBID, RecordingMbid: recordingMbid,
ReleaseMBID: t.Album.ReleaseMBID, ReleaseMbid: models.MBID(t.Album.ReleaseMbid),
ArtistMBIDs: []mbtypes.MBID{t.Artist.ArtistMBID}, ArtistMbids: []models.MBID{models.MBID(t.Artist.ArtistMbid)},
Tags: t.Tags, Tags: t.Tags,
AdditionalInfo: map[string]any{ AdditionalInfo: map[string]any{
"media_player": FunkwhaleClientName, "media_player": FunkwhaleClientName,

View file

@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uploadedlobster.com/scotty/internal/backends/funkwhale" "go.uploadedlobster.com/scotty/internal/backends/funkwhale"
"go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/models"
) )
func TestFromConfig(t *testing.T) { func TestFromConfig(t *testing.T) {
@ -43,17 +44,17 @@ func TestFunkwhaleListeningAsListen(t *testing.T) {
}, },
Track: funkwhale.Track{ Track: funkwhale.Track{
Title: "Oweynagat", Title: "Oweynagat",
RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12",
Position: 5, Position: 5,
DiscNumber: 1, DiscNumber: 1,
Tags: []string{"foo", "bar"}, Tags: []string{"foo", "bar"},
Artist: funkwhale.Artist{ Artist: funkwhale.Artist{
Name: "Dool", Name: "Dool",
ArtistMBID: "24412926-c7bd-48e8-afad-8a285b42e131", ArtistMbid: "24412926-c7bd-48e8-afad-8a285b42e131",
}, },
Album: funkwhale.Album{ Album: funkwhale.Album{
Title: "Here Now, There Then", Title: "Here Now, There Then",
ReleaseMBID: "d7f22677-9803-4d21-ba42-081b633a6f68", ReleaseMbid: "d7f22677-9803-4d21-ba42-081b633a6f68",
}, },
Uploads: []funkwhale.Upload{ Uploads: []funkwhale.Upload{
{ {
@ -74,9 +75,9 @@ func TestFunkwhaleListeningAsListen(t *testing.T) {
assert.Equal(fwListen.Track.DiscNumber, listen.Track.DiscNumber) assert.Equal(fwListen.Track.DiscNumber, listen.Track.DiscNumber)
assert.Equal(fwListen.Track.Tags, listen.Track.Tags) assert.Equal(fwListen.Track.Tags, listen.Track.Tags)
// assert.Equal(backends.FunkwhaleClientName, listen.AdditionalInfo["disc_number"]) // assert.Equal(backends.FunkwhaleClientName, listen.AdditionalInfo["disc_number"])
assert.Equal(fwListen.Track.RecordingMBID, listen.RecordingMBID) assert.Equal(models.MBID(fwListen.Track.RecordingMbid), listen.RecordingMbid)
assert.Equal(fwListen.Track.Album.ReleaseMBID, listen.ReleaseMBID) assert.Equal(models.MBID(fwListen.Track.Album.ReleaseMbid), listen.ReleaseMbid)
assert.Equal(fwListen.Track.Artist.ArtistMBID, listen.ArtistMBIDs[0]) assert.Equal(models.MBID(fwListen.Track.Artist.ArtistMbid), listen.ArtistMbids[0])
assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"]) assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"])
} }
@ -88,17 +89,17 @@ func TestFunkwhaleFavoriteTrackAsLove(t *testing.T) {
}, },
Track: funkwhale.Track{ Track: funkwhale.Track{
Title: "Oweynagat", Title: "Oweynagat",
RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12",
Position: 5, Position: 5,
DiscNumber: 1, DiscNumber: 1,
Tags: []string{"foo", "bar"}, Tags: []string{"foo", "bar"},
Artist: funkwhale.Artist{ Artist: funkwhale.Artist{
Name: "Dool", Name: "Dool",
ArtistMBID: "24412926-c7bd-48e8-afad-8a285b42e131", ArtistMbid: "24412926-c7bd-48e8-afad-8a285b42e131",
}, },
Album: funkwhale.Album{ Album: funkwhale.Album{
Title: "Here Now, There Then", Title: "Here Now, There Then",
ReleaseMBID: "d7f22677-9803-4d21-ba42-081b633a6f68", ReleaseMbid: "d7f22677-9803-4d21-ba42-081b633a6f68",
}, },
Uploads: []funkwhale.Upload{ Uploads: []funkwhale.Upload{
{ {
@ -118,10 +119,10 @@ func TestFunkwhaleFavoriteTrackAsLove(t *testing.T) {
assert.Equal(favorite.Track.Position, love.Track.TrackNumber) assert.Equal(favorite.Track.Position, love.Track.TrackNumber)
assert.Equal(favorite.Track.DiscNumber, love.Track.DiscNumber) assert.Equal(favorite.Track.DiscNumber, love.Track.DiscNumber)
assert.Equal(favorite.Track.Tags, love.Track.Tags) assert.Equal(favorite.Track.Tags, love.Track.Tags)
assert.Equal(favorite.Track.RecordingMBID, love.RecordingMBID) assert.Equal(models.MBID(favorite.Track.RecordingMbid), love.RecordingMbid)
assert.Equal(favorite.Track.RecordingMBID, love.Track.RecordingMBID) assert.Equal(models.MBID(favorite.Track.RecordingMbid), love.Track.RecordingMbid)
assert.Equal(favorite.Track.Album.ReleaseMBID, love.ReleaseMBID) assert.Equal(models.MBID(favorite.Track.Album.ReleaseMbid), love.ReleaseMbid)
require.Len(t, love.Track.ArtistMBIDs, 1) require.Len(t, love.Track.ArtistMbids, 1)
assert.Equal(favorite.Track.Artist.ArtistMBID, love.ArtistMBIDs[0]) assert.Equal(models.MBID(favorite.Track.Artist.ArtistMbid), love.ArtistMbids[0])
assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"]) assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"])
} }

View file

@ -21,8 +21,6 @@ THE SOFTWARE.
*/ */
package funkwhale package funkwhale
import "go.uploadedlobster.com/mbtypes"
type ListeningsResult struct { type ListeningsResult struct {
Count int `json:"count"` Count int `json:"count"`
Previous string `json:"previous"` Previous string `json:"previous"`
@ -52,30 +50,30 @@ type FavoriteTrack struct {
} }
type Track struct { type Track struct {
Id int `json:"int"` Id int `json:"int"`
Artist Artist `json:"artist"` Artist Artist `json:"artist"`
Album Album `json:"album"` Album Album `json:"album"`
Title string `json:"title"` Title string `json:"title"`
Position int `json:"position"` Position int `json:"position"`
DiscNumber int `json:"disc_number"` DiscNumber int `json:"disc_number"`
RecordingMBID mbtypes.MBID `json:"mbid"` RecordingMbid string `json:"mbid"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
Uploads []Upload `json:"uploads"` Uploads []Upload `json:"uploads"`
} }
type Artist struct { type Artist struct {
Id int `json:"int"` Id int `json:"int"`
Name string `json:"name"` Name string `json:"name"`
ArtistMBID mbtypes.MBID `json:"mbid"` ArtistMbid string `json:"mbid"`
} }
type Album struct { type Album struct {
Id int `json:"int"` Id int `json:"int"`
Title string `json:"title"` Title string `json:"title"`
AlbumArtist Artist `json:"artist"` AlbumArtist Artist `json:"artist"`
ReleaseDate string `json:"release_date"` ReleaseDate string `json:"release_date"`
TrackCount int `json:"track_count"` TrackCount int `json:"track_count"`
ReleaseMBID mbtypes.MBID `json:"mbid"` ReleaseMbid string `json:"mbid"`
} }
type User struct { type User struct {

View file

@ -118,8 +118,8 @@ func listenAsTrack(l models.Listen) jspf.Track {
extension.AddedBy = l.UserName extension.AddedBy = l.UserName
track.Extension[jspf.MusicBrainzTrackExtensionId] = extension track.Extension[jspf.MusicBrainzTrackExtensionId] = extension
if l.RecordingMBID != "" { if l.RecordingMbid != "" {
track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(l.RecordingMBID)) track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(l.RecordingMbid))
} }
return track return track
@ -133,12 +133,12 @@ func loveAsTrack(l models.Love) jspf.Track {
extension.AddedBy = l.UserName extension.AddedBy = l.UserName
track.Extension[jspf.MusicBrainzTrackExtensionId] = extension track.Extension[jspf.MusicBrainzTrackExtensionId] = extension
recordingMBID := l.Track.RecordingMBID recordingMbid := l.Track.RecordingMbid
if l.RecordingMBID != "" { if l.RecordingMbid != "" {
recordingMBID = l.RecordingMBID recordingMbid = l.RecordingMbid
} }
if recordingMBID != "" { if recordingMbid != "" {
track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(recordingMBID)) track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(recordingMbid))
} }
return track return track
@ -159,15 +159,15 @@ func trackAsTrack(t models.Track) jspf.Track {
func makeMusicBrainzExtension(t models.Track) jspf.MusicBrainzTrackExtension { func makeMusicBrainzExtension(t models.Track) jspf.MusicBrainzTrackExtension {
extension := jspf.MusicBrainzTrackExtension{ extension := jspf.MusicBrainzTrackExtension{
AdditionalMetadata: t.AdditionalInfo, AdditionalMetadata: t.AdditionalInfo,
ArtistIdentifiers: make([]string, len(t.ArtistMBIDs)), ArtistIdentifiers: make([]string, len(t.ArtistMbids)),
} }
for i, mbid := range t.ArtistMBIDs { for i, mbid := range t.ArtistMbids {
extension.ArtistIdentifiers[i] = "https://musicbrainz.org/artist/" + string(mbid) extension.ArtistIdentifiers[i] = "https://musicbrainz.org/artist/" + string(mbid)
} }
if t.ReleaseMBID != "" { if t.ReleaseMbid != "" {
extension.ReleaseIdentifier = "https://musicbrainz.org/release/" + string(t.ReleaseMBID) extension.ReleaseIdentifier = "https://musicbrainz.org/release/" + string(t.ReleaseMbid)
} }
// The tracknumber tag would be redundant // The tracknumber tag would be redundant

View file

@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/shkh/lastfm-go/lastfm" "github.com/shkh/lastfm-go/lastfm"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/auth" "go.uploadedlobster.com/scotty/internal/auth"
"go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/i18n"
@ -141,16 +140,16 @@ out:
TrackName: scrobble.Name, TrackName: scrobble.Name,
ArtistNames: []string{}, ArtistNames: []string{},
ReleaseName: scrobble.Album.Name, ReleaseName: scrobble.Album.Name,
RecordingMBID: mbtypes.MBID(scrobble.Mbid), RecordingMbid: models.MBID(scrobble.Mbid),
ArtistMBIDs: []mbtypes.MBID{}, ArtistMbids: []models.MBID{},
ReleaseMBID: mbtypes.MBID(scrobble.Album.Mbid), ReleaseMbid: models.MBID(scrobble.Album.Mbid),
}, },
} }
if scrobble.Artist.Name != "" { if scrobble.Artist.Name != "" {
listen.Track.ArtistNames = []string{scrobble.Artist.Name} listen.Track.ArtistNames = []string{scrobble.Artist.Name}
} }
if scrobble.Artist.Mbid != "" { if scrobble.Artist.Mbid != "" {
listen.Track.ArtistMBIDs = []mbtypes.MBID{mbtypes.MBID(scrobble.Artist.Mbid)} listen.Track.ArtistMbids = []models.MBID{models.MBID(scrobble.Artist.Mbid)}
} }
listens = append(listens, listen) listens = append(listens, listen)
} else { } else {
@ -204,8 +203,8 @@ func (b *LastfmApiBackend) ImportListens(export models.ListensResult, importResu
if l.TrackNumber > 0 { if l.TrackNumber > 0 {
trackNumbers = append(trackNumbers, strconv.Itoa(l.TrackNumber)) trackNumbers = append(trackNumbers, strconv.Itoa(l.TrackNumber))
} }
if l.RecordingMBID != "" { if l.RecordingMbid != "" {
mbids = append(mbids, string(l.RecordingMBID)) mbids = append(mbids, string(l.RecordingMbid))
} }
// if l.ReleaseArtist != "" { // if l.ReleaseArtist != "" {
// albumArtists = append(albums, l.ReleaseArtist) // albumArtists = append(albums, l.ReleaseArtist)
@ -295,12 +294,12 @@ out:
love := models.Love{ love := models.Love{
Created: time.Unix(timestamp, 0), Created: time.Unix(timestamp, 0),
UserName: result.User, UserName: result.User,
RecordingMBID: mbtypes.MBID(track.Mbid), RecordingMbid: models.MBID(track.Mbid),
Track: models.Track{ Track: models.Track{
TrackName: track.Name, TrackName: track.Name,
ArtistNames: []string{track.Artist.Name}, ArtistNames: []string{track.Artist.Name},
RecordingMBID: mbtypes.MBID(track.Mbid), RecordingMbid: models.MBID(track.Mbid),
ArtistMBIDs: []mbtypes.MBID{mbtypes.MBID(track.Artist.Mbid)}, ArtistMbids: []models.MBID{models.MBID(track.Artist.Mbid)},
AdditionalInfo: models.AdditionalInfo{ AdditionalInfo: models.AdditionalInfo{
"lastfm_url": track.Url, "lastfm_url": track.Url,
}, },

View file

@ -29,7 +29,6 @@ import (
"github.com/jarcoal/httpmock" "github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/backends/listenbrainz" "go.uploadedlobster.com/scotty/internal/backends/listenbrainz"
) )
@ -115,7 +114,7 @@ func TestGetFeedback(t *testing.T) {
assert.Equal(302, result.TotalCount) assert.Equal(302, result.TotalCount)
assert.Equal(3, result.Offset) assert.Equal(3, result.Offset)
require.Len(t, result.Feedback, 2) require.Len(t, result.Feedback, 2)
assert.Equal(mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), result.Feedback[0].RecordingMBID) assert.Equal("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", result.Feedback[0].RecordingMbid)
} }
func TestSendFeedback(t *testing.T) { func TestSendFeedback(t *testing.T) {
@ -132,7 +131,7 @@ func TestSendFeedback(t *testing.T) {
httpmock.RegisterResponder("POST", url, responder) httpmock.RegisterResponder("POST", url, responder)
feedback := listenbrainz.Feedback{ feedback := listenbrainz.Feedback{
RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12",
Score: 1, Score: 1,
} }
result, err := client.SendFeedback(feedback) result, err := client.SendFeedback(feedback)
@ -155,7 +154,7 @@ func TestLookup(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
assert.Equal("Say Just Words", result.RecordingName) assert.Equal("Say Just Words", result.RecordingName)
assert.Equal("Paradise Lost", result.ArtistCreditName) assert.Equal("Paradise Lost", result.ArtistCreditName)
assert.Equal(mbtypes.MBID("569436a1-234a-44bc-a370-8f4d252bef21"), result.RecordingMBID) assert.Equal("569436a1-234a-44bc-a370-8f4d252bef21", result.RecordingMbid)
} }
func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) {

View file

@ -21,7 +21,6 @@ import (
"sort" "sort"
"time" "time"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/i18n"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
@ -33,7 +32,7 @@ type ListenBrainzApiBackend struct {
client Client client Client
username string username string
checkDuplicates bool checkDuplicates bool
existingMBIDs map[mbtypes.MBID]bool existingMbids map[string]bool
} }
func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" } func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" }
@ -148,7 +147,7 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo
} else if isDupe { } else if isDupe {
count -= 1 count -= 1
msg := i18n.Tr("Ignored duplicate listen %v: \"%v\" by %v (%v)", msg := i18n.Tr("Ignored duplicate listen %v: \"%v\" by %v (%v)",
l.ListenedAt, l.TrackName, l.ArtistName(), l.RecordingMBID) l.ListenedAt, l.TrackName, l.ArtistName(), l.RecordingMbid)
importResult.Log(models.Info, msg) importResult.Log(models.Info, msg)
continue continue
} }
@ -230,7 +229,7 @@ out:
} }
func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
if len(b.existingMBIDs) == 0 { if len(b.existingMbids) == 0 {
existingLovesChan := make(chan models.LovesResult) existingLovesChan := make(chan models.LovesResult)
go b.ExportLoves(time.Unix(0, 0), existingLovesChan, progress) go b.ExportLoves(time.Unix(0, 0), existingLovesChan, progress)
existingLoves := <-existingLovesChan existingLoves := <-existingLovesChan
@ -239,30 +238,30 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe
} }
// TODO: Store MBIDs directly // TODO: Store MBIDs directly
b.existingMBIDs = make(map[mbtypes.MBID]bool, len(existingLoves.Items)) b.existingMbids = make(map[string]bool, len(existingLoves.Items))
for _, love := range existingLoves.Items { for _, love := range existingLoves.Items {
b.existingMBIDs[love.RecordingMBID] = true b.existingMbids[string(love.RecordingMbid)] = true
} }
} }
for _, love := range export.Items { for _, love := range export.Items {
recordingMBID := love.RecordingMBID recordingMbid := string(love.RecordingMbid)
if recordingMBID == "" { if recordingMbid == "" {
lookup, err := b.client.Lookup(love.TrackName, love.ArtistName()) lookup, err := b.client.Lookup(love.TrackName, love.ArtistName())
if err == nil { if err == nil {
recordingMBID = lookup.RecordingMBID recordingMbid = lookup.RecordingMbid
} }
} }
if recordingMBID != "" { if recordingMbid != "" {
ok := false ok := false
errMsg := "" errMsg := ""
if b.existingMBIDs[recordingMBID] { if b.existingMbids[recordingMbid] {
ok = true ok = true
} else { } else {
resp, err := b.client.SendFeedback(Feedback{ resp, err := b.client.SendFeedback(Feedback{
RecordingMBID: recordingMBID, RecordingMbid: recordingMbid,
Score: 1, Score: 1,
}) })
ok = err == nil && resp.Status == "ok" ok = err == nil && resp.Status == "ok"
@ -324,20 +323,20 @@ func (lbListen Listen) AsListen() models.Listen {
} }
func (f Feedback) AsLove() models.Love { func (f Feedback) AsLove() models.Love {
recordingMBID := f.RecordingMBID recordingMbid := models.MBID(f.RecordingMbid)
track := f.TrackMetadata track := f.TrackMetadata
if track == nil { if track == nil {
track = &Track{} track = &Track{}
} }
love := models.Love{ love := models.Love{
UserName: f.UserName, UserName: f.UserName,
RecordingMBID: recordingMBID, RecordingMbid: recordingMbid,
Created: time.Unix(f.Created, 0), Created: time.Unix(f.Created, 0),
Track: track.AsTrack(), Track: track.AsTrack(),
} }
if love.Track.RecordingMBID == "" { if love.Track.RecordingMbid == "" {
love.Track.RecordingMBID = love.RecordingMBID love.Track.RecordingMbid = love.RecordingMbid
} }
return love return love
@ -351,16 +350,16 @@ func (t Track) AsTrack() models.Track {
Duration: t.Duration(), Duration: t.Duration(),
TrackNumber: t.TrackNumber(), TrackNumber: t.TrackNumber(),
DiscNumber: t.DiscNumber(), DiscNumber: t.DiscNumber(),
RecordingMBID: t.RecordingMBID(), RecordingMbid: models.MBID(t.RecordingMbid()),
ReleaseMBID: t.ReleaseMBID(), ReleaseMbid: models.MBID(t.ReleaseMbid()),
ReleaseGroupMBID: t.ReleaseGroupMBID(), ReleaseGroupMbid: models.MBID(t.ReleaseGroupMbid()),
ISRC: t.ISRC(), ISRC: t.ISRC(),
AdditionalInfo: t.AdditionalInfo, AdditionalInfo: t.AdditionalInfo,
} }
if t.MBIDMapping != nil && len(track.ArtistMBIDs) == 0 { if t.MbidMapping != nil && len(track.ArtistMbids) == 0 {
for _, artistMBID := range t.MBIDMapping.ArtistMBIDs { for _, artistMbid := range t.MbidMapping.ArtistMbids {
track.ArtistMBIDs = append(track.ArtistMBIDs, artistMBID) track.ArtistMbids = append(track.ArtistMbids, models.MBID(artistMbid))
} }
} }

View file

@ -23,9 +23,9 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/backends/listenbrainz" "go.uploadedlobster.com/scotty/internal/backends/listenbrainz"
"go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/models"
) )
func TestFromConfig(t *testing.T) { func TestFromConfig(t *testing.T) {
@ -65,30 +65,30 @@ func TestListenBrainzListenAsListen(t *testing.T) {
assert.Equal(t, []string{lbListen.TrackMetadata.ArtistName}, listen.ArtistNames) assert.Equal(t, []string{lbListen.TrackMetadata.ArtistName}, listen.ArtistNames)
assert.Equal(t, 5, listen.TrackNumber) assert.Equal(t, 5, listen.TrackNumber)
assert.Equal(t, 1, listen.DiscNumber) assert.Equal(t, 1, listen.DiscNumber)
assert.Equal(t, mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMBID) assert.Equal(t, models.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMbid)
assert.Equal(t, mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMBID) assert.Equal(t, models.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMbid)
assert.Equal(t, mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMBID) assert.Equal(t, models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMbid)
assert.Equal(t, mbtypes.ISRC("DES561620801"), listen.ISRC) assert.Equal(t, "DES561620801", listen.ISRC)
assert.Equal(t, lbListen.TrackMetadata.AdditionalInfo["foo"], listen.AdditionalInfo["foo"]) assert.Equal(t, lbListen.TrackMetadata.AdditionalInfo["foo"], listen.AdditionalInfo["foo"])
} }
func TestListenBrainzFeedbackAsLove(t *testing.T) { func TestListenBrainzFeedbackAsLove(t *testing.T) {
recordingMBID := mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12") recordingMbid := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"
releaseMBID := mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68") releaseMbid := "d7f22677-9803-4d21-ba42-081b633a6f68"
artistMBID := mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68") artistMbid := "d7f22677-9803-4d21-ba42-081b633a6f68"
feedback := listenbrainz.Feedback{ feedback := listenbrainz.Feedback{
Created: 1699859066, Created: 1699859066,
RecordingMBID: recordingMBID, RecordingMbid: recordingMbid,
Score: 1, Score: 1,
UserName: "ousidecontext", UserName: "ousidecontext",
TrackMetadata: &listenbrainz.Track{ TrackMetadata: &listenbrainz.Track{
TrackName: "Oweynagat", TrackName: "Oweynagat",
ArtistName: "Dool", ArtistName: "Dool",
ReleaseName: "Here Now, There Then", ReleaseName: "Here Now, There Then",
MBIDMapping: &listenbrainz.MBIDMapping{ MbidMapping: &listenbrainz.MbidMapping{
RecordingMBID: recordingMBID, RecordingMbid: recordingMbid,
ReleaseMBID: releaseMBID, ReleaseMbid: releaseMbid,
ArtistMBIDs: []mbtypes.MBID{artistMBID}, ArtistMbids: []string{artistMbid},
}, },
}, },
} }
@ -99,24 +99,24 @@ func TestListenBrainzFeedbackAsLove(t *testing.T) {
assert.Equal(feedback.TrackMetadata.TrackName, love.TrackName) assert.Equal(feedback.TrackMetadata.TrackName, love.TrackName)
assert.Equal(feedback.TrackMetadata.ReleaseName, love.ReleaseName) assert.Equal(feedback.TrackMetadata.ReleaseName, love.ReleaseName)
assert.Equal([]string{feedback.TrackMetadata.ArtistName}, love.ArtistNames) assert.Equal([]string{feedback.TrackMetadata.ArtistName}, love.ArtistNames)
assert.Equal(recordingMBID, love.RecordingMBID) assert.Equal(models.MBID(recordingMbid), love.RecordingMbid)
assert.Equal(recordingMBID, love.Track.RecordingMBID) assert.Equal(models.MBID(recordingMbid), love.Track.RecordingMbid)
assert.Equal(releaseMBID, love.Track.ReleaseMBID) assert.Equal(models.MBID(releaseMbid), love.Track.ReleaseMbid)
require.Len(t, love.Track.ArtistMBIDs, 1) require.Len(t, love.Track.ArtistMbids, 1)
assert.Equal(artistMBID, love.Track.ArtistMBIDs[0]) assert.Equal(models.MBID(artistMbid), love.Track.ArtistMbids[0])
} }
func TestListenBrainzPartialFeedbackAsLove(t *testing.T) { func TestListenBrainzPartialFeedbackAsLove(t *testing.T) {
recordingMBID := mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12") recordingMbid := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"
feedback := listenbrainz.Feedback{ feedback := listenbrainz.Feedback{
Created: 1699859066, Created: 1699859066,
RecordingMBID: recordingMBID, RecordingMbid: recordingMbid,
Score: 1, Score: 1,
} }
love := feedback.AsLove() love := feedback.AsLove()
assert := assert.New(t) assert := assert.New(t)
assert.Equal(time.Unix(1699859066, 0).Unix(), love.Created.Unix()) assert.Equal(time.Unix(1699859066, 0).Unix(), love.Created.Unix())
assert.Equal(recordingMBID, love.RecordingMBID) assert.Equal(models.MBID(recordingMbid), love.RecordingMbid)
assert.Equal(recordingMBID, love.Track.RecordingMBID) assert.Equal(models.MBID(recordingMbid), love.Track.RecordingMbid)
assert.Empty(love.Track.TrackName) assert.Empty(love.Track.TrackName)
} }

View file

@ -25,7 +25,6 @@ import (
"strconv" "strconv"
"time" "time"
"go.uploadedlobster.com/mbtypes"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
) )
@ -67,20 +66,20 @@ type Track struct {
ArtistName string `json:"artist_name,omitempty"` ArtistName string `json:"artist_name,omitempty"`
ReleaseName string `json:"release_name,omitempty"` ReleaseName string `json:"release_name,omitempty"`
AdditionalInfo map[string]any `json:"additional_info,omitempty"` AdditionalInfo map[string]any `json:"additional_info,omitempty"`
MBIDMapping *MBIDMapping `json:"mbid_mapping,omitempty"` MbidMapping *MbidMapping `json:"mbid_mapping,omitempty"`
} }
type MBIDMapping struct { type MbidMapping struct {
RecordingName string `json:"recording_name,omitempty"` RecordingName string `json:"recording_name,omitempty"`
RecordingMBID mbtypes.MBID `json:"recording_mbid,omitempty"` RecordingMbid string `json:"recording_mbid,omitempty"`
ReleaseMBID mbtypes.MBID `json:"release_mbid,omitempty"` ReleaseMbid string `json:"release_mbid,omitempty"`
ArtistMBIDs []mbtypes.MBID `json:"artist_mbids,omitempty"` ArtistMbids []string `json:"artist_mbids,omitempty"`
Artists []Artist `json:"artists,omitempty"` Artists []Artist `json:"artists,omitempty"`
} }
type Artist struct { type Artist struct {
ArtistCreditName string `json:"artist_credit_name,omitempty"` ArtistCreditName string `json:"artist_credit_name,omitempty"`
ArtistMBID string `json:"artist_mbid,omitempty"` ArtistMbid string `json:"artist_mbid,omitempty"`
JoinPhrase string `json:"join_phrase,omitempty"` JoinPhrase string `json:"join_phrase,omitempty"`
} }
@ -92,21 +91,21 @@ type GetFeedbackResult struct {
} }
type Feedback struct { type Feedback struct {
Created int64 `json:"created,omitempty"` Created int64 `json:"created,omitempty"`
RecordingMBID mbtypes.MBID `json:"recording_mbid,omitempty"` RecordingMbid string `json:"recording_mbid,omitempty"`
RecordingMsid mbtypes.MBID `json:"recording_msid,omitempty"` RecordingMsid string `json:"recording_msid,omitempty"`
Score int `json:"score,omitempty"` Score int `json:"score,omitempty"`
TrackMetadata *Track `json:"track_metadata,omitempty"` TrackMetadata *Track `json:"track_metadata,omitempty"`
UserName string `json:"user_id,omitempty"` UserName string `json:"user_id,omitempty"`
} }
type LookupResult struct { type LookupResult struct {
ArtistCreditName string `json:"artist_credit_name"` ArtistCreditName string `json:"artist_credit_name"`
ReleaseName string `json:"release_name"` ReleaseName string `json:"release_name"`
RecordingName string `json:"recording_name"` RecordingName string `json:"recording_name"`
RecordingMBID mbtypes.MBID `json:"recording_mbid"` RecordingMbid string `json:"recording_mbid"`
ReleaseMBID mbtypes.MBID `json:"release_mbid"` ReleaseMbid string `json:"release_mbid"`
ArtistMBIDs []mbtypes.MBID `json:"artist_mbids"` ArtistMbids []string `json:"artist_mbids"`
} }
type StatusResult struct { type StatusResult struct {
@ -159,30 +158,30 @@ func (t Track) DiscNumber() int {
return 0 return 0
} }
func (t Track) ISRC() mbtypes.ISRC { func (t Track) ISRC() string {
return mbtypes.ISRC(tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc")) return tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc")
} }
func (t Track) RecordingMBID() mbtypes.MBID { func (t Track) RecordingMbid() string {
mbid := mbtypes.MBID(tryGetValueOrEmpty[string](t.AdditionalInfo, "recording_mbid")) mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "recording_mbid")
if mbid == "" && t.MBIDMapping != nil { if mbid == "" && t.MbidMapping != nil {
return t.MBIDMapping.RecordingMBID return t.MbidMapping.RecordingMbid
} else { } else {
return mbid return mbid
} }
} }
func (t Track) ReleaseMBID() mbtypes.MBID { func (t Track) ReleaseMbid() string {
mbid := mbtypes.MBID(tryGetValueOrEmpty[string](t.AdditionalInfo, "release_mbid")) mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "release_mbid")
if mbid == "" && t.MBIDMapping != nil { if mbid == "" && t.MbidMapping != nil {
return t.MBIDMapping.ReleaseMBID return t.MbidMapping.ReleaseMbid
} else { } else {
return mbid return mbid
} }
} }
func (t Track) ReleaseGroupMBID() mbtypes.MBID { func (t Track) ReleaseGroupMbid() string {
return mbtypes.MBID(tryGetValueOrEmpty[string](t.AdditionalInfo, "release_group_mbid")) return tryGetValueOrEmpty[string](t.AdditionalInfo, "release_group_mbid")
} }
func tryGetValueOrEmpty[T any](dict map[string]any, key string) T { func tryGetValueOrEmpty[T any](dict map[string]any, key string) T {

View file

@ -28,7 +28,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/backends/listenbrainz" "go.uploadedlobster.com/scotty/internal/backends/listenbrainz"
) )
@ -132,49 +131,49 @@ func TestTrackTrackNumberString(t *testing.T) {
} }
func TestTrackIsrc(t *testing.T) { func TestTrackIsrc(t *testing.T) {
expected := mbtypes.ISRC("TCAEJ1934417") expected := "TCAEJ1934417"
track := listenbrainz.Track{ track := listenbrainz.Track{
AdditionalInfo: map[string]any{ AdditionalInfo: map[string]any{
"isrc": string(expected), "isrc": expected,
}, },
} }
assert.Equal(t, expected, track.ISRC()) assert.Equal(t, expected, track.ISRC())
} }
func TestTrackRecordingMBID(t *testing.T) { func TestTrackRecordingMbid(t *testing.T) {
expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b") expected := "e02cc1c3-93fd-4e24-8b77-325060de920b"
track := listenbrainz.Track{ track := listenbrainz.Track{
AdditionalInfo: map[string]any{ AdditionalInfo: map[string]any{
"recording_mbid": string(expected), "recording_mbid": expected,
}, },
} }
assert.Equal(t, expected, track.RecordingMBID()) assert.Equal(t, expected, track.RecordingMbid())
} }
func TestTrackReleaseMBID(t *testing.T) { func TestTrackReleaseMbid(t *testing.T) {
expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b") expected := "e02cc1c3-93fd-4e24-8b77-325060de920b"
track := listenbrainz.Track{ track := listenbrainz.Track{
AdditionalInfo: map[string]any{ AdditionalInfo: map[string]any{
"release_mbid": string(expected), "release_mbid": expected,
}, },
} }
assert.Equal(t, expected, track.ReleaseMBID()) assert.Equal(t, expected, track.ReleaseMbid())
} }
func TestReleaseGroupMBID(t *testing.T) { func TestReleaseGroupMbid(t *testing.T) {
expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b") expected := "e02cc1c3-93fd-4e24-8b77-325060de920b"
track := listenbrainz.Track{ track := listenbrainz.Track{
AdditionalInfo: map[string]any{ AdditionalInfo: map[string]any{
"release_group_mbid": string(expected), "release_group_mbid": expected,
}, },
} }
assert.Equal(t, expected, track.ReleaseGroupMBID()) assert.Equal(t, expected, track.ReleaseGroupMbid())
} }
func TestMarshalPartialFeedback(t *testing.T) { func TestMarshalPartialFeedback(t *testing.T) {
feedback := listenbrainz.Feedback{ feedback := listenbrainz.Feedback{
Created: 1699859066, Created: 1699859066,
RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", RecordingMbid: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12",
} }
b, err := json.Marshal(feedback) b, err := json.Marshal(feedback)
require.NoError(t, err) require.NoError(t, err)

View file

@ -30,7 +30,6 @@ import (
"strings" "strings"
"time" "time"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
) )
@ -58,7 +57,7 @@ func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) {
for { for {
// A row is: // A row is:
// artistName releaseName trackName trackNumber duration rating timestamp recordingMBID // artistName releaseName trackName trackNumber duration rating timestamp recordingMbid
row, err := tsvReader.Read() row, err := tsvReader.Read()
if err == io.EOF { if err == io.EOF {
break break
@ -101,7 +100,7 @@ func Write(data io.Writer, listens models.ListensList) (lastTimestamp time.Time,
} }
// A row is: // A row is:
// artistName releaseName trackName trackNumber duration rating timestamp recordingMBID // artistName releaseName trackName trackNumber duration rating timestamp recordingMbid
rating, ok := listen.AdditionalInfo["rockbox_rating"].(string) rating, ok := listen.AdditionalInfo["rockbox_rating"].(string)
if !ok || rating == "" { if !ok || rating == "" {
rating = "L" rating = "L"
@ -114,7 +113,7 @@ func Write(data io.Writer, listens models.ListensList) (lastTimestamp time.Time,
strconv.Itoa(int(listen.Duration.Seconds())), strconv.Itoa(int(listen.Duration.Seconds())),
rating, rating,
strconv.Itoa(int(listen.ListenedAt.Unix())), strconv.Itoa(int(listen.ListenedAt.Unix())),
string(listen.RecordingMBID), string(listen.RecordingMbid),
}) })
} }
@ -204,7 +203,7 @@ func rowToListen(row []string, client string) (models.Listen, error) {
} }
if len(row) > 7 { if len(row) > 7 {
listen.Track.RecordingMBID = mbtypes.MBID(row[7]) listen.Track.RecordingMbid = models.MBID(row[7])
} }
return listen, nil return listen, nil

View file

@ -30,7 +30,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/backends/scrobblerlog" "go.uploadedlobster.com/scotty/internal/backends/scrobblerlog"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
) )
@ -61,10 +60,10 @@ func TestParser(t *testing.T) {
assert.Equal(time.Duration(306*time.Second), listen1.Duration) assert.Equal(time.Duration(306*time.Second), listen1.Duration)
assert.Equal("L", listen1.AdditionalInfo["rockbox_rating"]) assert.Equal("L", listen1.AdditionalInfo["rockbox_rating"])
assert.Equal(time.Unix(1260342084, 0), listen1.ListenedAt) assert.Equal(time.Unix(1260342084, 0), listen1.ListenedAt)
assert.Equal(mbtypes.MBID(""), listen1.RecordingMBID) assert.Equal(models.MBID(""), listen1.RecordingMbid)
listen4 := result.Listens[3] listen4 := result.Listens[3]
assert.Equal("S", listen4.AdditionalInfo["rockbox_rating"]) assert.Equal("S", listen4.AdditionalInfo["rockbox_rating"])
assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMBID) assert.Equal(models.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMbid)
} }
func TestParserExcludeSkipped(t *testing.T) { func TestParserExcludeSkipped(t *testing.T) {
@ -75,7 +74,7 @@ func TestParserExcludeSkipped(t *testing.T) {
assert.Len(result.Listens, 4) assert.Len(result.Listens, 4)
listen4 := result.Listens[3] listen4 := result.Listens[3]
assert.Equal("L", listen4.AdditionalInfo["rockbox_rating"]) assert.Equal("L", listen4.AdditionalInfo["rockbox_rating"])
assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMBID) assert.Equal(models.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMbid)
} }
func TestWrite(t *testing.T) { func TestWrite(t *testing.T) {
@ -94,7 +93,7 @@ func TestWrite(t *testing.T) {
TrackName: "Reign", TrackName: "Reign",
TrackNumber: 1, TrackNumber: 1,
Duration: 271 * time.Second, Duration: 271 * time.Second,
RecordingMBID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"), RecordingMbid: models.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"),
AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"}, AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"},
}, },
}, },

View file

@ -22,8 +22,6 @@ THE SOFTWARE.
package spotify package spotify
import "go.uploadedlobster.com/mbtypes"
type TracksResult struct { type TracksResult struct {
Href string `json:"href"` Href string `json:"href"`
Limit int `json:"limit"` Limit int `json:"limit"`
@ -100,9 +98,9 @@ type Artist struct {
} }
type ExternalIds struct { type ExternalIds struct {
ISRC mbtypes.ISRC `json:"isrc"` ISRC string `json:"isrc"`
EAN string `json:"ean"` EAN string `json:"ean"`
UPC string `json:"upc"` UPC string `json:"upc"`
} }
type ExternalUrls struct { type ExternalUrls struct {

View file

@ -26,7 +26,6 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/backends/spotify" "go.uploadedlobster.com/scotty/internal/backends/spotify"
"go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/config"
) )
@ -60,7 +59,7 @@ func TestSpotifyListenAsListen(t *testing.T) {
assert.Equal(t, []string{"Dool"}, listen.ArtistNames) assert.Equal(t, []string{"Dool"}, listen.ArtistNames)
assert.Equal(t, 5, listen.TrackNumber) assert.Equal(t, 5, listen.TrackNumber)
assert.Equal(t, 1, listen.DiscNumber) assert.Equal(t, 1, listen.DiscNumber)
assert.Equal(t, mbtypes.ISRC("DES561620801"), listen.ISRC) assert.Equal(t, "DES561620801", listen.ISRC)
info := listen.AdditionalInfo info := listen.AdditionalInfo
assert.Equal(t, "spotify.com", info["music_service"]) assert.Equal(t, "spotify.com", info["music_service"])
assert.Equal(t, "https://open.spotify.com/track/2JKUgGuXK3dEvyuIJ4Yj2V", info["origin_url"]) assert.Equal(t, "https://open.spotify.com/track/2JKUgGuXK3dEvyuIJ4Yj2V", info["origin_url"])

View file

@ -24,10 +24,9 @@ package models
import ( import (
"strings" "strings"
"time" "time"
"go.uploadedlobster.com/mbtypes"
) )
type MBID string
type Entity string type Entity string
const ( const (
@ -44,12 +43,12 @@ type Track struct {
TrackNumber int TrackNumber int
DiscNumber int DiscNumber int
Duration time.Duration Duration time.Duration
ISRC mbtypes.ISRC ISRC string
RecordingMBID mbtypes.MBID RecordingMbid MBID
ReleaseMBID mbtypes.MBID ReleaseMbid MBID
ReleaseGroupMBID mbtypes.MBID ReleaseGroupMbid MBID
ArtistMBIDs []mbtypes.MBID ArtistMbids []MBID
WorkMBIDs []mbtypes.MBID WorkMbids []MBID
Tags []string Tags []string
AdditionalInfo AdditionalInfo AdditionalInfo AdditionalInfo
} }
@ -63,20 +62,20 @@ func (t *Track) FillAdditionalInfo() {
if t.AdditionalInfo == nil { if t.AdditionalInfo == nil {
t.AdditionalInfo = make(AdditionalInfo, 5) t.AdditionalInfo = make(AdditionalInfo, 5)
} }
if t.RecordingMBID != "" { if t.RecordingMbid != "" {
t.AdditionalInfo["recording_mbid"] = t.RecordingMBID t.AdditionalInfo["recording_mbid"] = t.RecordingMbid
} }
if t.ReleaseGroupMBID != "" { if t.ReleaseGroupMbid != "" {
t.AdditionalInfo["release_group_mbid"] = t.ReleaseGroupMBID t.AdditionalInfo["release_group_mbid"] = t.ReleaseGroupMbid
} }
if t.ReleaseMBID != "" { if t.ReleaseMbid != "" {
t.AdditionalInfo["release_mbid"] = t.ReleaseMBID t.AdditionalInfo["release_mbid"] = t.ReleaseMbid
} }
if len(t.ArtistMBIDs) > 0 { if len(t.ArtistMbids) > 0 {
t.AdditionalInfo["artist_mbids"] = t.ArtistMBIDs t.AdditionalInfo["artist_mbids"] = t.ArtistMbids
} }
if len(t.WorkMBIDs) > 0 { if len(t.WorkMbids) > 0 {
t.AdditionalInfo["work_mbids"] = t.WorkMBIDs t.AdditionalInfo["work_mbids"] = t.WorkMbids
} }
if t.ISRC != "" { if t.ISRC != "" {
t.AdditionalInfo["isrc"] = t.ISRC t.AdditionalInfo["isrc"] = t.ISRC
@ -111,8 +110,8 @@ type Love struct {
Track Track
Created time.Time Created time.Time
UserName string UserName string
RecordingMBID mbtypes.MBID RecordingMbid MBID
RecordingMsid mbtypes.MBID RecordingMsid MBID
} }
type ListensList []Listen type ListensList []Listen

View file

@ -28,7 +28,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
) )
@ -45,25 +44,25 @@ func TestTrackArtistName(t *testing.T) {
func TestTrackFillAdditionalInfo(t *testing.T) { func TestTrackFillAdditionalInfo(t *testing.T) {
track := models.Track{ track := models.Track{
RecordingMBID: mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), RecordingMbid: models.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"),
ReleaseGroupMBID: mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), ReleaseGroupMbid: models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"),
ReleaseMBID: mbtypes.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"), ReleaseMbid: models.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"),
ArtistMBIDs: []mbtypes.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"}, ArtistMbids: []models.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"},
WorkMBIDs: []mbtypes.MBID{"c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"}, WorkMbids: []models.MBID{"c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"},
TrackNumber: 5, TrackNumber: 5,
DiscNumber: 1, DiscNumber: 1,
Duration: time.Duration(413787 * time.Millisecond), Duration: time.Duration(413787 * time.Millisecond),
ISRC: mbtypes.ISRC("DES561620801"), ISRC: "DES561620801",
Tags: []string{"rock", "psychedelic rock"}, Tags: []string{"rock", "psychedelic rock"},
} }
track.FillAdditionalInfo() track.FillAdditionalInfo()
i := track.AdditionalInfo i := track.AdditionalInfo
assert := assert.New(t) assert := assert.New(t)
assert.Equal(track.RecordingMBID, i["recording_mbid"]) assert.Equal(track.RecordingMbid, i["recording_mbid"])
assert.Equal(track.ReleaseGroupMBID, i["release_group_mbid"]) assert.Equal(track.ReleaseGroupMbid, i["release_group_mbid"])
assert.Equal(track.ReleaseMBID, i["release_mbid"]) assert.Equal(track.ReleaseMbid, i["release_mbid"])
assert.Equal(track.ArtistMBIDs, i["artist_mbids"]) assert.Equal(track.ArtistMbids, i["artist_mbids"])
assert.Equal(track.WorkMBIDs, i["work_mbids"]) assert.Equal(track.WorkMbids, i["work_mbids"])
assert.Equal(track.TrackNumber, i["tracknumber"]) assert.Equal(track.TrackNumber, i["tracknumber"])
assert.Equal(track.DiscNumber, i["discnumber"]) assert.Equal(track.DiscNumber, i["discnumber"])
assert.Equal(track.Duration.Milliseconds(), i["duration_ms"]) assert.Equal(track.Duration.Milliseconds(), i["duration_ms"])

View file

@ -63,7 +63,7 @@ func NormalizeTitle(s string) string {
// Compare two tracks for similarity. // Compare two tracks for similarity.
func CompareTracks(t1 models.Track, t2 models.Track) float64 { func CompareTracks(t1 models.Track, t2 models.Track) float64 {
// Identical recording MBID always compares 100% // Identical recording MBID always compares 100%
if t1.RecordingMBID == t2.RecordingMBID && t1.RecordingMBID != "" { if t1.RecordingMbid == t2.RecordingMbid && t1.RecordingMbid != "" {
return 1.0 return 1.0
} }

View file

@ -20,7 +20,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
"go.uploadedlobster.com/scotty/internal/similarity" "go.uploadedlobster.com/scotty/internal/similarity"
) )
@ -75,13 +74,13 @@ func TestCompareTracksSameMBID(t *testing.T) {
t1 := models.Track{ t1 := models.Track{
ArtistNames: []string{"Paradise Lost"}, ArtistNames: []string{"Paradise Lost"},
TrackName: "Forever After", TrackName: "Forever After",
RecordingMBID: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), RecordingMbid: models.MBID("2886d15c-09b0-43c6-af56-932f70dde164"),
} }
t2 := models.Track{ t2 := models.Track{
ArtistNames: []string{"Paradise Lost"}, ArtistNames: []string{"Paradise Lost"},
TrackName: "Forever Failure (radio edit)", TrackName: "Forever Failure (radio edit)",
ReleaseName: "Draconian Times", ReleaseName: "Draconian Times",
RecordingMBID: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"), RecordingMbid: models.MBID("2886d15c-09b0-43c6-af56-932f70dde164"),
} }
assert.Equal(t, 1.0, similarity.CompareTracks(t1, t2)) assert.Equal(t, 1.0, similarity.CompareTracks(t1, t2))
} }

View file

@ -42,56 +42,56 @@ var messageKeyToIndex = map[string]int{
"\tbackend: %v": 11, "\tbackend: %v": 11,
"\texport: %s": 0, "\texport: %s": 0,
"\timport: %s\n": 1, "\timport: %s\n": 1,
"%v: %v": 47, "%v: %v": 52,
"Aborted": 8, "Aborted": 8,
"Access token": 19, "Access token": 19,
"Access token received, you can use %v now.\n": 33, "Access token received, you can use %v now.\n": 28,
"Append to file": 21, "Append to file": 21,
"Backend": 41, "Backend": 36,
"Check for duplicate listens on import (slower)": 24, "Check for duplicate listens on import (slower)": 45,
"Client ID": 15, "Client ID": 15,
"Client secret": 16, "Client secret": 16,
"Delete the service configuration \"%v\"?": 7, "Delete the service configuration \"%v\"?": 7,
"Directory path": 27, "Directory path": 47,
"Disable auto correction of submitted listens": 25, "Disable auto correction of submitted listens": 24,
"Error: OAuth state mismatch": 32, "Error: OAuth state mismatch": 27,
"Failed reading config: %v": 2, "Failed reading config: %v": 2,
"File path": 20, "File path": 20,
"From timestamp: %v (%v)": 43, "From timestamp: %v (%v)": 38,
"Ignore listens in incognito mode": 28, "Ignore listens in incognito mode": 48,
"Ignore skipped listens": 29, "Ignore skipped listens": 49,
"Ignored duplicate listen %v: \"%v\" by %v (%v)": 53, "Ignored duplicate listen %v: \"%v\" by %v (%v)": 46,
"Import failed, last reported timestamp was %v (%s)": 44, "Import failed, last reported timestamp was %v (%s)": 39,
"Import log:": 46, "Import log:": 51,
"Imported %v of %v %s into %v.": 45, "Imported %v of %v %s into %v.": 40,
"Include skipped listens": 26, "Include skipped listens": 25,
"Latest timestamp: %v (%v)": 49, "Latest timestamp: %v (%v)": 41,
"Minimum playback duration for skipped tracks (seconds)": 30, "Minimum playback duration for skipped tracks (seconds)": 50,
"No": 38, "No": 33,
"Playlist title": 22, "Playlist title": 22,
"Saved service %v using backend %v": 5, "Saved service %v using backend %v": 5,
"Server URL": 17, "Server URL": 17,
"Service": 40, "Service": 35,
"Service \"%v\" deleted\n": 9, "Service \"%v\" deleted\n": 9,
"Service name": 3, "Service name": 3,
"The backend %v requires authentication. Authenticate now?": 6, "The backend %v requires authentication. Authenticate now?": 6,
"Token received, you can close this window now.": 12, "Token received, you can close this window now.": 12,
"Transferring %s from %s to %s...": 42, "Transferring %s from %s to %s...": 37,
"Unique playlist identifier": 23, "Unique playlist identifier": 23,
"Updated service %v using backend %v\n": 10, "Updated service %v using backend %v\n": 10,
"User name": 18, "User name": 18,
"Visit the URL for authorization: %v": 31, "Visit the URL for authorization: %v": 26,
"Yes": 37, "Yes": 32,
"a service with this name already exists": 4, "a service with this name already exists": 4,
"backend %s does not implement %s": 13, "backend %s does not implement %s": 13,
"done": 36, "done": 31,
"exporting": 34, "exporting": 29,
"importing": 35, "importing": 30,
"invalid timestamp string \"%v\"": 48, "invalid timestamp string \"%v\"": 53,
"key must only consist of A-Za-z0-9_-": 51, "key must only consist of A-Za-z0-9_-": 43,
"no configuration file defined, cannot write config": 50, "no configuration file defined, cannot write config": 42,
"no existing service configurations": 39, "no existing service configurations": 34,
"no service configuration \"%v\"": 52, "no service configuration \"%v\"": 44,
"unknown backend \"%s\"": 14, "unknown backend \"%s\"": 14,
} }
@ -103,18 +103,18 @@ var deIndex = []uint32{ // 55 elements
0x000001ac, 0x000001e7, 0x00000213, 0x00000233, 0x000001ac, 0x000001e7, 0x00000213, 0x00000233,
0x0000023d, 0x0000024b, 0x00000256, 0x00000263, 0x0000023d, 0x0000024b, 0x00000256, 0x00000263,
0x00000271, 0x0000027b, 0x0000028e, 0x000002a1, 0x00000271, 0x0000027b, 0x0000028e, 0x000002a1,
0x000002b8, 0x000002ed, 0x00000321, 0x00000342, 0x000002b8, 0x000002ec, 0x0000030d, 0x00000333,
0x00000352, 0x00000378, 0x0000039a, 0x000003d8, 0x0000035d, 0x0000039d, 0x000003a8, 0x000003b3,
// Entry 20 - 3F // Entry 20 - 3F
0x000003fe, 0x00000428, 0x00000468, 0x00000473, 0x000003ba, 0x000003bd, 0x000003c2, 0x000003eb,
0x0000047e, 0x00000485, 0x00000488, 0x0000048d, 0x000003f3, 0x000003fb, 0x00000424, 0x00000442,
0x000004b6, 0x000004be, 0x000004c6, 0x000004ef, 0x0000047f, 0x000004aa, 0x000004cd, 0x0000051e,
0x0000050d, 0x0000054a, 0x00000575, 0x00000580, 0x00000555, 0x0000057c, 0x0000057c, 0x0000057c,
0x0000058d, 0x000005b1, 0x000005d4, 0x00000625, 0x0000057c, 0x0000057c, 0x0000057c, 0x0000057c,
0x0000065c, 0x00000683, 0x00000683, 0x0000057c, 0x0000057c, 0x0000057c,
} // Size: 244 bytes } // Size: 244 bytes
const deData string = "" + // Size: 1667 bytes const deData string = "" + // Size: 1404 bytes
"\x04\x01\x09\x00\x0e\x02Export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02Import:" + "\x04\x01\x09\x00\x0e\x02Export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02Import:" +
" %[1]s\x02Fehler beim Lesen der Konfiguration: %[1]v\x02Servicename\x02e" + " %[1]s\x02Fehler beim Lesen der Konfiguration: %[1]v\x02Servicename\x02e" +
"in Service mit diesem Namen existiert bereits\x02Service %[1]v mit dem B" + "in Service mit diesem Namen existiert bereits\x02Service %[1]v mit dem B" +
@ -126,22 +126,18 @@ const deData string = "" + // Size: 1667 bytes
" geschlossen werden.\x02das backend %[1]s implementiert %[2]s nicht\x02u" + " geschlossen werden.\x02das backend %[1]s implementiert %[2]s nicht\x02u" +
"nbekanntes Backend „%[1]s“\x02Client-ID\x02Client-Secret\x02Server-URL" + "nbekanntes Backend „%[1]s“\x02Client-ID\x02Client-Secret\x02Server-URL" +
"\x02Benutzername\x02Zugriffstoken\x02Dateipfad\x02An Datei anhängen\x02T" + "\x02Benutzername\x02Zugriffstoken\x02Dateipfad\x02An Datei anhängen\x02T" +
"itel der Playlist\x02Eindeutige Playlist-ID\x02Beim Import auf Listen-Du" + "itel der Playlist\x02Eindeutige Playlist-ID\x02Autokorrektur für übermit" +
"plikate prüfen (langsamer)\x02Autokorrektur für übermittelte Titel deakt" + "telte Titel deaktivieren\x02Übersprungene Titel einbeziehen\x02URL für A" +
"ivieren\x02Übersprungene Titel einbeziehen\x02Verzeichnispfad\x02Listens" + "utorisierung öffnen: %[1]v\x02Fehler: OAuth-State stimmt nicht überein" +
" im Inkognito-Modus ignorieren\x02Übersprungene Listens ignorieren\x02Mi" + "\x04\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet " +
"nimale Wiedergabedauer für übersprungene Titel (Sekunden)\x02URL für Aut" + "werden.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bes" +
"orisierung öffnen: %[1]v\x02Fehler: OAuth-State stimmt nicht überein\x04" + "tehenden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s" +
"\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet werd" + " von %[2]s nach %[3]s...\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehl" +
"en.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bestehe" + "geschlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3" +
"nden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s von" + "]s in %[4]v importiert.\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine K" +
" %[2]s nach %[3]s...\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehlgesc" + "onfigurationsdatei definiert, Konfiguration kann nicht geschrieben werde" +
"hlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3]s i" + "n\x02Schlüssel darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Serv" +
"n %[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Zeitstem" + "icekonfiguration „%[1]v“"
"pel „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine Konfiguration" +
"sdatei definiert, Konfiguration kann nicht geschrieben werden\x02Schlüss" +
"el darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Servicekonfigura" +
"tion „%[1]v“"
var enIndex = []uint32{ // 55 elements var enIndex = []uint32{ // 55 elements
// Entry 0 - 1F // Entry 0 - 1F
@ -151,15 +147,15 @@ var enIndex = []uint32{ // 55 elements
0x00000170, 0x0000019f, 0x000001c6, 0x000001de, 0x00000170, 0x0000019f, 0x000001c6, 0x000001de,
0x000001e8, 0x000001f6, 0x00000201, 0x0000020b, 0x000001e8, 0x000001f6, 0x00000201, 0x0000020b,
0x00000218, 0x00000222, 0x00000231, 0x00000240, 0x00000218, 0x00000222, 0x00000231, 0x00000240,
0x0000025b, 0x0000028a, 0x000002b7, 0x000002cf, 0x0000025b, 0x00000288, 0x000002a0, 0x000002c7,
0x000002de, 0x000002ff, 0x00000316, 0x0000034d, 0x000002e3, 0x00000316, 0x00000320, 0x0000032a,
// Entry 20 - 3F // Entry 20 - 3F
0x00000374, 0x00000390, 0x000003c3, 0x000003cd, 0x0000032f, 0x00000333, 0x00000336, 0x00000359,
0x000003d7, 0x000003dc, 0x000003e0, 0x000003e3, 0x00000361, 0x00000369, 0x00000393, 0x000003b1,
0x00000406, 0x0000040e, 0x00000416, 0x00000440, 0x000003ea, 0x00000414, 0x00000434, 0x00000467,
0x0000045e, 0x00000497, 0x000004c1, 0x000004cd, 0x0000048c, 0x000004ad, 0x000004dc, 0x00000515,
0x000004da, 0x000004fb, 0x0000051b, 0x0000054e, 0x00000524, 0x00000545, 0x0000055c, 0x00000593,
0x00000573, 0x00000594, 0x000005cd, 0x0000059f, 0x000005ac, 0x000005cd,
} // Size: 244 bytes } // Size: 244 bytes
const enData string = "" + // Size: 1485 bytes const enData string = "" + // Size: 1485 bytes
@ -173,20 +169,20 @@ const enData string = "" + // Size: 1485 bytes
"eceived, you can close this window now.\x02backend %[1]s does not implem" + "eceived, you can close this window now.\x02backend %[1]s does not implem" +
"ent %[2]s\x02unknown backend \x22%[1]s\x22\x02Client ID\x02Client secret" + "ent %[2]s\x02unknown backend \x22%[1]s\x22\x02Client ID\x02Client secret" +
"\x02Server URL\x02User name\x02Access token\x02File path\x02Append to fi" + "\x02Server URL\x02User name\x02Access token\x02File path\x02Append to fi" +
"le\x02Playlist title\x02Unique playlist identifier\x02Check for duplicat" + "le\x02Playlist title\x02Unique playlist identifier\x02Disable auto corre" +
"e listens on import (slower)\x02Disable auto correction of submitted lis" + "ction of submitted listens\x02Include skipped listens\x02Visit the URL f" +
"tens\x02Include skipped listens\x02Directory path\x02Ignore listens in i" + "or authorization: %[1]v\x02Error: OAuth state mismatch\x04\x00\x01\x0a." +
"ncognito mode\x02Ignore skipped listens\x02Minimum playback duration for" + "\x02Access token received, you can use %[1]v now.\x02exporting\x02import" +
" skipped tracks (seconds)\x02Visit the URL for authorization: %[1]v\x02E" + "ing\x02done\x02Yes\x02No\x02no existing service configurations\x02Servic" +
"rror: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token received, yo" + "e\x02Backend\x02Transferring %[1]s from %[2]s to %[3]s...\x02From timest" +
"u can use %[1]v now.\x02exporting\x02importing\x02done\x02Yes\x02No\x02n" + "amp: %[1]v (%[2]v)\x02Import failed, last reported timestamp was %[1]v (" +
"o existing service configurations\x02Service\x02Backend\x02Transferring " + "%[2]s)\x02Imported %[1]v of %[2]v %[3]s into %[4]v.\x02Latest timestamp:" +
"%[1]s from %[2]s to %[3]s...\x02From timestamp: %[1]v (%[2]v)\x02Import " + " %[1]v (%[2]v)\x02no configuration file defined, cannot write config\x02" +
"failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v of %" + "key must only consist of A-Za-z0-9_-\x02no service configuration \x22%[1" +
"[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid timesta" + "]v\x22\x02Check for duplicate listens on import (slower)\x02Ignored dupl" +
"mp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no configu" + "icate listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)\x02Directory path\x02" +
"ration file defined, cannot write config\x02key must only consist of A-Z" + "Ignore listens in incognito mode\x02Ignore skipped listens\x02Minimum pl" +
"a-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored duplicate " + "ayback duration for skipped tracks (seconds)\x02Import log:\x02%[1]v: %[" +
"listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)" "2]v\x02invalid timestamp string \x22%[1]v\x22"
// Total table size 3640 bytes (3KiB); checksum: 719A868A // Total table size 3377 bytes (3KiB); checksum: 6715024

View file

@ -258,11 +258,11 @@
{ {
"id": "Check for duplicate listens on import (slower)", "id": "Check for duplicate listens on import (slower)",
"message": "Check for duplicate listens on import (slower)", "message": "Check for duplicate listens on import (slower)",
"translation": "Beim Import auf Listen-Duplikate prüfen (langsamer)" "translation": ""
}, },
{ {
"id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})",
"message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})",
"translation": "", "translation": "",
"placeholders": [ "placeholders": [
{ {
@ -290,12 +290,12 @@
"expr": "l.ArtistName()" "expr": "l.ArtistName()"
}, },
{ {
"id": "RecordingMBID", "id": "RecordingMbid",
"string": "%[4]v", "string": "%[4]v",
"type": "go.uploadedlobster.com/mbtypes.MBID", "type": "go.uploadedlobster.com/scotty/internal/models.MBID",
"underlyingType": "string", "underlyingType": "string",
"argNum": 4, "argNum": 4,
"expr": "l.RecordingMBID" "expr": "l.RecordingMbid"
} }
] ]
}, },
@ -312,22 +312,22 @@
{ {
"id": "Directory path", "id": "Directory path",
"message": "Directory path", "message": "Directory path",
"translation": "Verzeichnispfad" "translation": ""
}, },
{ {
"id": "Ignore listens in incognito mode", "id": "Ignore listens in incognito mode",
"message": "Ignore listens in incognito mode", "message": "Ignore listens in incognito mode",
"translation": "Listens im Inkognito-Modus ignorieren" "translation": ""
}, },
{ {
"id": "Ignore skipped listens", "id": "Ignore skipped listens",
"message": "Ignore skipped listens", "message": "Ignore skipped listens",
"translation": "Übersprungene Listens ignorieren" "translation": ""
}, },
{ {
"id": "Minimum playback duration for skipped tracks (seconds)", "id": "Minimum playback duration for skipped tracks (seconds)",
"message": "Minimum playback duration for skipped tracks (seconds)", "message": "Minimum playback duration for skipped tracks (seconds)",
"translation": "Minimale Wiedergabedauer für übersprungene Titel (Sekunden)" "translation": ""
}, },
{ {
"id": "Visit the URL for authorization: {Url}", "id": "Visit the URL for authorization: {Url}",
@ -525,12 +525,12 @@
{ {
"id": "Import log:", "id": "Import log:",
"message": "Import log:", "message": "Import log:",
"translation": "Importlog:" "translation": ""
}, },
{ {
"id": "{Type}: {Message}", "id": "{Type}: {Message}",
"message": "{Type}: {Message}", "message": "{Type}: {Message}",
"translation": "{Type}: {Message}", "translation": "",
"placeholders": [ "placeholders": [
{ {
"id": "Type", "id": "Type",
@ -553,7 +553,7 @@
{ {
"id": "invalid timestamp string \"{FlagValue}\"", "id": "invalid timestamp string \"{FlagValue}\"",
"message": "invalid timestamp string \"{FlagValue}\"", "message": "invalid timestamp string \"{FlagValue}\"",
"translation": "ungültiger Zeitstempel „{FlagValue}“", "translation": "",
"placeholders": [ "placeholders": [
{ {
"id": "FlagValue", "id": "FlagValue",

View file

@ -311,9 +311,9 @@
"fuzzy": true "fuzzy": true
}, },
{ {
"id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})",
"message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "message": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})",
"translation": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})", "translation": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMbid})",
"translatorComment": "Copied from source.", "translatorComment": "Copied from source.",
"placeholders": [ "placeholders": [
{ {
@ -341,12 +341,12 @@
"expr": "l.ArtistName()" "expr": "l.ArtistName()"
}, },
{ {
"id": "RecordingMBID", "id": "RecordingMbid",
"string": "%[4]v", "string": "%[4]v",
"type": "go.uploadedlobster.com/mbtypes.MBID", "type": "go.uploadedlobster.com/scotty/internal/models.MBID",
"underlyingType": "string", "underlyingType": "string",
"argNum": 4, "argNum": 4,
"expr": "l.RecordingMBID" "expr": "l.RecordingMbid"
} }
], ],
"fuzzy": true "fuzzy": true

View file

@ -6,4 +6,4 @@ package are published under the conditions of CC0 1.0 Universal (CC0 1.0)
package translations package translations
//go:generate go tool gotext -srclang=en update -out=catalog.go -lang=en,de go.uploadedlobster.com/scotty //go:generate gotext -srclang=en update -out=catalog.go -lang=en,de go.uploadedlobster.com/scotty