mirror of
https://git.sr.ht/~phw/scotty
synced 2025-04-19 19:49:28 +02:00
Compare commits
16 commits
Author | SHA1 | Date | |
---|---|---|---|
|
1ea90d2d2b | ||
|
329f696b55 | ||
|
5f9c0f24ab | ||
|
dc834e9b6f | ||
|
0d9bc74bc0 | ||
|
13eb8342ab | ||
|
ad1644672c | ||
|
8fff19ceac | ||
|
04eddfda33 | ||
|
1c1ce224f7 | ||
|
7175d3453d | ||
|
cdf20728ae | ||
|
bcc7bf3167 | ||
|
357932f9b0 | ||
|
3f1bebd8ed | ||
|
1aa7b61649 |
39 changed files with 514 additions and 565 deletions
|
@ -1,5 +1,11 @@
|
||||||
# Scotty Changelog
|
# Scotty Changelog
|
||||||
|
|
||||||
|
## 0.4.1 - 2024-09-16
|
||||||
|
- Subsonic: include `subsonic_id` as additional metadata
|
||||||
|
- Deezer: fix artist and album ID URIs (#7)
|
||||||
|
- Fix installation issues due to wrong go version format in `go.mod`
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2024-01-21
|
## 0.4.0 - 2024-01-21
|
||||||
- JSPF: implement append mode
|
- JSPF: implement append mode
|
||||||
- scrobberlog: append mode is enabled by default
|
- scrobberlog: append mode is enabled by default
|
||||||
|
|
87
go.mod
87
go.mod
|
@ -1,70 +1,73 @@
|
||||||
module go.uploadedlobster.com/scotty
|
module go.uploadedlobster.com/scotty
|
||||||
|
|
||||||
go 1.21.1
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Xuanwo/go-locale v1.1.0
|
github.com/Xuanwo/go-locale v1.1.3
|
||||||
github.com/agnivade/levenshtein v1.1.1
|
github.com/agnivade/levenshtein v1.2.1
|
||||||
github.com/cli/browser v1.3.0
|
github.com/cli/browser v1.3.0
|
||||||
github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5
|
github.com/delucks/go-subsonic v0.0.0-20240806025900-2a743ec36238
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/glebarez/sqlite v1.10.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-resty/resty/v2 v2.11.0
|
github.com/go-resty/resty/v2 v2.16.5
|
||||||
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.1.1
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
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.6.0
|
github.com/spf13/cast v1.7.1
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.20.1
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/vbauerster/mpb/v8 v8.7.2
|
github.com/vbauerster/mpb/v8 v8.9.3
|
||||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3
|
go.uploadedlobster.com/mbtypes v0.4.0
|
||||||
golang.org/x/oauth2 v0.16.0
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/oauth2 v0.29.0
|
||||||
gorm.io/datatypes v1.2.0
|
golang.org/x/text v0.24.0
|
||||||
gorm.io/gorm v1.25.5
|
gorm.io/datatypes v1.2.5
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||||
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.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.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.7.1 // indirect
|
github.com/go-sql-driver/mysql v1.9.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/google/uuid v1.5.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/magiconair/properties v1.8.7 // indirect
|
github.com/mattn/go-colorable v0.1.14 // 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.15 // 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/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.4 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.9.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.11.0 // indirect
|
github.com/spf13/afero v1.14.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.6 // 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/net v0.20.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
golang.org/x/sync v0.13.0 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.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.2 // indirect
|
gorm.io/driver/mysql v1.5.7 // indirect
|
||||||
modernc.org/libc v1.40.2 // indirect
|
modernc.org/libc v1.62.1 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.7.2 // indirect
|
modernc.org/memory v1.9.1 // indirect
|
||||||
modernc.org/sqlite v1.28.0 // indirect
|
modernc.org/sqlite v1.37.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tool golang.org/x/text/cmd/gotext
|
||||||
|
|
294
go.sum
294
go.sum
|
@ -1,11 +1,13 @@
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
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.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg=
|
github.com/Xuanwo/go-locale v1.1.3 h1:EWZZJJt5rqPHHbqPRH1zFCn5D7xHjjebODctA4aUO3A=
|
||||||
github.com/Xuanwo/go-locale v1.1.0/go.mod h1:UKrHoZB3FPIk9wIG2/tVSobnHgNnceGSH3Y8DY5cASs=
|
github.com/Xuanwo/go-locale v1.1.3/go.mod h1:REn+F/c+AtGSWYACBSYZgl23AP+0lfQC+SEFPN+hj30=
|
||||||
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.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||||
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
|
||||||
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=
|
||||||
|
@ -19,234 +21,178 @@ 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.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
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-20240806025900-2a743ec36238 h1:uejyepOdHISrJTw7P84Y7yEC0FMyv1q3KNDRxWsviKw=
|
||||||
github.com/delucks/go-subsonic v0.0.0-20220915164742-2744002c4be5/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-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
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.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
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.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
|
||||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
|
||||||
github.com/google/uuid v1.5.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-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
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.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||||
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.15/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.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
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 v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
|
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
||||||
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
|
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
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=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
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.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||||
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.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
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.7.2 h1:SMJtxhNho1MV3OuFgS1DAzhANN1Ejc5Ct+0iSaIkB14=
|
github.com/vbauerster/mpb/v8 v8.9.3 h1:PnMeF+sMvYv9u23l6DO6Q3+Mdj408mjLRXIzmUmU2Z8=
|
||||||
github.com/vbauerster/mpb/v8 v8.7.2/go.mod h1:ZFnrjzspgDHoxYLGvxIruiNk73GNTPG4YHgVNpR10VY=
|
github.com/vbauerster/mpb/v8 v8.9.3/go.mod h1:hxS8Hz4C6ijnppDSIX6LjG8FYJSoPo9iIOcE53Zik0c=
|
||||||
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=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
go.uploadedlobster.com/mbtypes v0.4.0 h1:D5asCgHsRWufj4Yn5u0IuH2J9z1UuYImYkYIp1Z1Q7s=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
go.uploadedlobster.com/mbtypes v0.4.0/go.mod h1:Bu1K1Hl77QTAE2Z7QKiW/JAp9KqYWQebkRRfG02dlZM=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
|
||||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
|
||||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
|
||||||
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/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
|
||||||
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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
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/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
|
||||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
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.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
|
gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I=
|
||||||
gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
|
gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4=
|
||||||
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
|
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.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
|
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
|
||||||
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
|
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
|
||||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
modernc.org/libc v1.40.2 h1:pzVHG9jwYZNWANfltHiU3HYfrzYIsX6ysRLJ93adZXA=
|
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
|
||||||
modernc.org/libc v1.40.2/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
|
||||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
|
||||||
|
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
|
||||||
|
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
||||||
|
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|
|
@ -85,7 +85,7 @@ func listRequest[T Result](c Client, path string, offset int, limit int) (result
|
||||||
}
|
}
|
||||||
response, err := request.Get(path)
|
response, err := request.Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(response.String())
|
err = errors.New(response.String())
|
||||||
} else if result.Error() != nil {
|
} else if result.Error() != nil {
|
||||||
err = errors.New(result.Error().Message)
|
err = errors.New(result.Error().Message)
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"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"
|
||||||
"go.uploadedlobster.com/scotty/internal/util"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -106,7 +105,7 @@ out:
|
||||||
// and continue.
|
// and continue.
|
||||||
if offset >= result.Total {
|
if offset >= result.Total {
|
||||||
p.Total = int64(result.Total)
|
p.Total = int64(result.Total)
|
||||||
offset = util.Max(result.Total-perPage, 0)
|
offset = max(result.Total-perPage, 0)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +174,7 @@ out:
|
||||||
if offset >= result.Total {
|
if offset >= result.Total {
|
||||||
p.Total = int64(result.Total)
|
p.Total = int64(result.Total)
|
||||||
totalCount = result.Total
|
totalCount = result.Total
|
||||||
offset = util.Max(result.Total-perPage, 0)
|
offset = max(result.Total-perPage, 0)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,8 +244,8 @@ func (t Track) AsTrack() models.Track {
|
||||||
info["music_service"] = "deezer.com"
|
info["music_service"] = "deezer.com"
|
||||||
info["origin_url"] = t.Link
|
info["origin_url"] = t.Link
|
||||||
info["deezer_id"] = t.Link
|
info["deezer_id"] = t.Link
|
||||||
info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/track/%v", t.Album.Id)
|
info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/album/%v", t.Album.Id)
|
||||||
info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/track/%v", t.Artist.Id)
|
info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/artist/%v", t.Artist.Id)
|
||||||
|
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,8 @@ func TestListenAsListen(t *testing.T) {
|
||||||
assert.Equal(t, "deezer.com", listen.AdditionalInfo["music_service"])
|
assert.Equal(t, "deezer.com", listen.AdditionalInfo["music_service"])
|
||||||
assert.Equal(t, "https://www.deezer.com/track/14631511", listen.AdditionalInfo["origin_url"])
|
assert.Equal(t, "https://www.deezer.com/track/14631511", listen.AdditionalInfo["origin_url"])
|
||||||
assert.Equal(t, "https://www.deezer.com/track/14631511", listen.AdditionalInfo["deezer_id"])
|
assert.Equal(t, "https://www.deezer.com/track/14631511", listen.AdditionalInfo["deezer_id"])
|
||||||
|
assert.Equal(t, "https://www.deezer.com/album/1346960", listen.AdditionalInfo["deezer_album_id"])
|
||||||
|
assert.Equal(t, "https://www.deezer.com/artist/92", listen.AdditionalInfo["deezer_artist_id"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLovedTrackAsLove(t *testing.T) {
|
func TestLovedTrackAsLove(t *testing.T) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (c Client) GetHistoryListenings(user string, page int, perPage int) (result
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(response.String())
|
err = errors.New(response.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ func (c Client) GetFavoriteTracks(page int, perPage int) (result FavoriteTracksR
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(response.String())
|
err = errors.New(response.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ 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"
|
||||||
|
@ -175,7 +176,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,16 +189,15 @@ 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: recordingMbid,
|
RecordingMBID: t.RecordingMBID,
|
||||||
ReleaseMbid: models.MBID(t.Album.ReleaseMbid),
|
ReleaseMBID: t.Album.ReleaseMBID,
|
||||||
ArtistMbids: []models.MBID{models.MBID(t.Artist.ArtistMbid)},
|
ArtistMBIDs: []mbtypes.MBID{t.Artist.ArtistMBID},
|
||||||
Tags: t.Tags,
|
Tags: t.Tags,
|
||||||
AdditionalInfo: map[string]any{
|
AdditionalInfo: map[string]any{
|
||||||
"media_player": FunkwhaleClientName,
|
"media_player": FunkwhaleClientName,
|
||||||
|
|
|
@ -25,7 +25,6 @@ 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) {
|
||||||
|
@ -44,17 +43,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{
|
||||||
{
|
{
|
||||||
|
@ -75,9 +74,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(models.MBID(fwListen.Track.RecordingMbid), listen.RecordingMbid)
|
assert.Equal(fwListen.Track.RecordingMBID, listen.RecordingMBID)
|
||||||
assert.Equal(models.MBID(fwListen.Track.Album.ReleaseMbid), listen.ReleaseMbid)
|
assert.Equal(fwListen.Track.Album.ReleaseMBID, listen.ReleaseMBID)
|
||||||
assert.Equal(models.MBID(fwListen.Track.Artist.ArtistMbid), listen.ArtistMbids[0])
|
assert.Equal(fwListen.Track.Artist.ArtistMBID, listen.ArtistMBIDs[0])
|
||||||
assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"])
|
assert.Equal(funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,17 +88,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{
|
||||||
{
|
{
|
||||||
|
@ -119,10 +118,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(models.MBID(favorite.Track.RecordingMbid), love.RecordingMbid)
|
assert.Equal(favorite.Track.RecordingMBID, love.RecordingMBID)
|
||||||
assert.Equal(models.MBID(favorite.Track.RecordingMbid), love.Track.RecordingMbid)
|
assert.Equal(favorite.Track.RecordingMBID, love.Track.RecordingMBID)
|
||||||
assert.Equal(models.MBID(favorite.Track.Album.ReleaseMbid), love.ReleaseMbid)
|
assert.Equal(favorite.Track.Album.ReleaseMBID, love.ReleaseMBID)
|
||||||
require.Len(t, love.Track.ArtistMbids, 1)
|
require.Len(t, love.Track.ArtistMBIDs, 1)
|
||||||
assert.Equal(models.MBID(favorite.Track.Artist.ArtistMbid), love.ArtistMbids[0])
|
assert.Equal(favorite.Track.Artist.ArtistMBID, love.ArtistMBIDs[0])
|
||||||
assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"])
|
assert.Equal(funkwhale.FunkwhaleClientName, love.AdditionalInfo["media_player"])
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ 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"`
|
||||||
|
@ -56,7 +58,7 @@ type Track struct {
|
||||||
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 string `json:"mbid"`
|
RecordingMBID mbtypes.MBID `json:"mbid"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
Uploads []Upload `json:"uploads"`
|
Uploads []Upload `json:"uploads"`
|
||||||
}
|
}
|
||||||
|
@ -64,7 +66,7 @@ type Track struct {
|
||||||
type Artist struct {
|
type Artist struct {
|
||||||
Id int `json:"int"`
|
Id int `json:"int"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ArtistMbid string `json:"mbid"`
|
ArtistMBID mbtypes.MBID `json:"mbid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Album struct {
|
type Album struct {
|
||||||
|
@ -73,7 +75,7 @@ type Album struct {
|
||||||
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 string `json:"mbid"`
|
ReleaseMBID mbtypes.MBID `json:"mbid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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"
|
||||||
|
@ -140,16 +141,16 @@ out:
|
||||||
TrackName: scrobble.Name,
|
TrackName: scrobble.Name,
|
||||||
ArtistNames: []string{},
|
ArtistNames: []string{},
|
||||||
ReleaseName: scrobble.Album.Name,
|
ReleaseName: scrobble.Album.Name,
|
||||||
RecordingMbid: models.MBID(scrobble.Mbid),
|
RecordingMBID: mbtypes.MBID(scrobble.Mbid),
|
||||||
ArtistMbids: []models.MBID{},
|
ArtistMBIDs: []mbtypes.MBID{},
|
||||||
ReleaseMbid: models.MBID(scrobble.Album.Mbid),
|
ReleaseMBID: mbtypes.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 = []models.MBID{models.MBID(scrobble.Artist.Mbid)}
|
listen.Track.ArtistMBIDs = []mbtypes.MBID{mbtypes.MBID(scrobble.Artist.Mbid)}
|
||||||
}
|
}
|
||||||
listens = append(listens, listen)
|
listens = append(listens, listen)
|
||||||
} else {
|
} else {
|
||||||
|
@ -203,8 +204,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)
|
||||||
|
@ -294,12 +295,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: models.MBID(track.Mbid),
|
RecordingMBID: mbtypes.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: models.MBID(track.Mbid),
|
RecordingMBID: mbtypes.MBID(track.Mbid),
|
||||||
ArtistMbids: []models.MBID{models.MBID(track.Artist.Mbid)},
|
ArtistMBIDs: []mbtypes.MBID{mbtypes.MBID(track.Artist.Mbid)},
|
||||||
AdditionalInfo: models.AdditionalInfo{
|
AdditionalInfo: models.AdditionalInfo{
|
||||||
"lastfm_url": track.Url,
|
"lastfm_url": track.Url,
|
||||||
},
|
},
|
||||||
|
|
|
@ -74,7 +74,7 @@ func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (r
|
||||||
SetError(&errorResult).
|
SetError(&errorResult).
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(errorResult.Error)
|
err = errors.New(errorResult.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, er
|
||||||
SetError(&errorResult).
|
SetError(&errorResult).
|
||||||
Post(path)
|
Post(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(errorResult.Error)
|
err = errors.New(errorResult.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ func (c Client) GetFeedback(user string, status int, offset int) (result GetFeed
|
||||||
SetError(&errorResult).
|
SetError(&errorResult).
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(errorResult.Error)
|
err = errors.New(errorResult.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ func (c Client) SendFeedback(feedback Feedback) (result StatusResult, err error)
|
||||||
SetError(&errorResult).
|
SetError(&errorResult).
|
||||||
Post(path)
|
Post(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(errorResult.Error)
|
err = errors.New(errorResult.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ func (c Client) Lookup(recordingName string, artistName string) (result LookupRe
|
||||||
SetError(&errorResult).
|
SetError(&errorResult).
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(errorResult.Error)
|
err = errors.New(errorResult.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -114,7 +115,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("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", result.Feedback[0].RecordingMbid)
|
assert.Equal(mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), result.Feedback[0].RecordingMBID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendFeedback(t *testing.T) {
|
func TestSendFeedback(t *testing.T) {
|
||||||
|
@ -131,7 +132,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)
|
||||||
|
@ -154,7 +155,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("569436a1-234a-44bc-a370-8f4d252bef21", result.RecordingMbid)
|
assert.Equal(mbtypes.MBID("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) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ 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"
|
||||||
|
@ -32,7 +33,7 @@ type ListenBrainzApiBackend struct {
|
||||||
client Client
|
client Client
|
||||||
username string
|
username string
|
||||||
checkDuplicates bool
|
checkDuplicates bool
|
||||||
existingMbids map[string]bool
|
existingMBIDs map[mbtypes.MBID]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" }
|
func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" }
|
||||||
|
@ -147,7 +148,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
|
||||||
}
|
}
|
||||||
|
@ -229,7 +230,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
|
||||||
|
@ -238,30 +239,30 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Store MBIDs directly
|
// TODO: Store MBIDs directly
|
||||||
b.existingMbids = make(map[string]bool, len(existingLoves.Items))
|
b.existingMBIDs = make(map[mbtypes.MBID]bool, len(existingLoves.Items))
|
||||||
for _, love := range existingLoves.Items {
|
for _, love := range existingLoves.Items {
|
||||||
b.existingMbids[string(love.RecordingMbid)] = true
|
b.existingMBIDs[love.RecordingMBID] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, love := range export.Items {
|
for _, love := range export.Items {
|
||||||
recordingMbid := string(love.RecordingMbid)
|
recordingMBID := 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"
|
||||||
|
@ -323,20 +324,20 @@ func (lbListen Listen) AsListen() models.Listen {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Feedback) AsLove() models.Love {
|
func (f Feedback) AsLove() models.Love {
|
||||||
recordingMbid := models.MBID(f.RecordingMbid)
|
recordingMBID := 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
|
||||||
|
@ -350,16 +351,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: models.MBID(t.RecordingMbid()),
|
RecordingMBID: t.RecordingMBID(),
|
||||||
ReleaseMbid: models.MBID(t.ReleaseMbid()),
|
ReleaseMBID: t.ReleaseMBID(),
|
||||||
ReleaseGroupMbid: models.MBID(t.ReleaseGroupMbid()),
|
ReleaseGroupMBID: 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, models.MBID(artistMbid))
|
track.ArtistMBIDs = append(track.ArtistMBIDs, artistMBID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, models.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMbid)
|
assert.Equal(t, mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMBID)
|
||||||
assert.Equal(t, models.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMbid)
|
assert.Equal(t, mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMBID)
|
||||||
assert.Equal(t, models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMbid)
|
assert.Equal(t, mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMBID)
|
||||||
assert.Equal(t, "DES561620801", listen.ISRC)
|
assert.Equal(t, mbtypes.ISRC("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 := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"
|
recordingMBID := mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12")
|
||||||
releaseMbid := "d7f22677-9803-4d21-ba42-081b633a6f68"
|
releaseMBID := mbtypes.MBID("d7f22677-9803-4d21-ba42-081b633a6f68")
|
||||||
artistMbid := "d7f22677-9803-4d21-ba42-081b633a6f68"
|
artistMBID := mbtypes.MBID("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: []string{artistMbid},
|
ArtistMBIDs: []mbtypes.MBID{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(models.MBID(recordingMbid), love.RecordingMbid)
|
assert.Equal(recordingMBID, love.RecordingMBID)
|
||||||
assert.Equal(models.MBID(recordingMbid), love.Track.RecordingMbid)
|
assert.Equal(recordingMBID, love.Track.RecordingMBID)
|
||||||
assert.Equal(models.MBID(releaseMbid), love.Track.ReleaseMbid)
|
assert.Equal(releaseMBID, love.Track.ReleaseMBID)
|
||||||
require.Len(t, love.Track.ArtistMbids, 1)
|
require.Len(t, love.Track.ArtistMBIDs, 1)
|
||||||
assert.Equal(models.MBID(artistMbid), love.Track.ArtistMbids[0])
|
assert.Equal(artistMBID, love.Track.ArtistMBIDs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListenBrainzPartialFeedbackAsLove(t *testing.T) {
|
func TestListenBrainzPartialFeedbackAsLove(t *testing.T) {
|
||||||
recordingMbid := "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"
|
recordingMBID := mbtypes.MBID("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(models.MBID(recordingMbid), love.RecordingMbid)
|
assert.Equal(recordingMBID, love.RecordingMBID)
|
||||||
assert.Equal(models.MBID(recordingMbid), love.Track.RecordingMbid)
|
assert.Equal(recordingMBID, love.Track.RecordingMBID)
|
||||||
assert.Empty(love.Track.TrackName)
|
assert.Empty(love.Track.TrackName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uploadedlobster.com/mbtypes"
|
||||||
"golang.org/x/exp/constraints"
|
"golang.org/x/exp/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,20 +67,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 string `json:"recording_mbid,omitempty"`
|
RecordingMBID mbtypes.MBID `json:"recording_mbid,omitempty"`
|
||||||
ReleaseMbid string `json:"release_mbid,omitempty"`
|
ReleaseMBID mbtypes.MBID `json:"release_mbid,omitempty"`
|
||||||
ArtistMbids []string `json:"artist_mbids,omitempty"`
|
ArtistMBIDs []mbtypes.MBID `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,8 +93,8 @@ type GetFeedbackResult struct {
|
||||||
|
|
||||||
type Feedback struct {
|
type Feedback struct {
|
||||||
Created int64 `json:"created,omitempty"`
|
Created int64 `json:"created,omitempty"`
|
||||||
RecordingMbid string `json:"recording_mbid,omitempty"`
|
RecordingMBID mbtypes.MBID `json:"recording_mbid,omitempty"`
|
||||||
RecordingMsid string `json:"recording_msid,omitempty"`
|
RecordingMsid mbtypes.MBID `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"`
|
||||||
|
@ -103,9 +104,9 @@ 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 string `json:"recording_mbid"`
|
RecordingMBID mbtypes.MBID `json:"recording_mbid"`
|
||||||
ReleaseMbid string `json:"release_mbid"`
|
ReleaseMBID mbtypes.MBID `json:"release_mbid"`
|
||||||
ArtistMbids []string `json:"artist_mbids"`
|
ArtistMBIDs []mbtypes.MBID `json:"artist_mbids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusResult struct {
|
type StatusResult struct {
|
||||||
|
@ -158,30 +159,30 @@ func (t Track) DiscNumber() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Track) ISRC() string {
|
func (t Track) ISRC() mbtypes.ISRC {
|
||||||
return tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc")
|
return mbtypes.ISRC(tryGetValueOrEmpty[string](t.AdditionalInfo, "isrc"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Track) RecordingMbid() string {
|
func (t Track) RecordingMBID() mbtypes.MBID {
|
||||||
mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "recording_mbid")
|
mbid := mbtypes.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() string {
|
func (t Track) ReleaseMBID() mbtypes.MBID {
|
||||||
mbid := tryGetValueOrEmpty[string](t.AdditionalInfo, "release_mbid")
|
mbid := mbtypes.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() string {
|
func (t Track) ReleaseGroupMBID() mbtypes.MBID {
|
||||||
return tryGetValueOrEmpty[string](t.AdditionalInfo, "release_group_mbid")
|
return mbtypes.MBID(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 {
|
||||||
|
|
|
@ -28,6 +28,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -131,49 +132,49 @@ func TestTrackTrackNumberString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrackIsrc(t *testing.T) {
|
func TestTrackIsrc(t *testing.T) {
|
||||||
expected := "TCAEJ1934417"
|
expected := mbtypes.ISRC("TCAEJ1934417")
|
||||||
track := listenbrainz.Track{
|
track := listenbrainz.Track{
|
||||||
AdditionalInfo: map[string]any{
|
AdditionalInfo: map[string]any{
|
||||||
"isrc": expected,
|
"isrc": string(expected),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, track.ISRC())
|
assert.Equal(t, expected, track.ISRC())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrackRecordingMbid(t *testing.T) {
|
func TestTrackRecordingMBID(t *testing.T) {
|
||||||
expected := "e02cc1c3-93fd-4e24-8b77-325060de920b"
|
expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b")
|
||||||
track := listenbrainz.Track{
|
track := listenbrainz.Track{
|
||||||
AdditionalInfo: map[string]any{
|
AdditionalInfo: map[string]any{
|
||||||
"recording_mbid": expected,
|
"recording_mbid": string(expected),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, track.RecordingMbid())
|
assert.Equal(t, expected, track.RecordingMBID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrackReleaseMbid(t *testing.T) {
|
func TestTrackReleaseMBID(t *testing.T) {
|
||||||
expected := "e02cc1c3-93fd-4e24-8b77-325060de920b"
|
expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b")
|
||||||
track := listenbrainz.Track{
|
track := listenbrainz.Track{
|
||||||
AdditionalInfo: map[string]any{
|
AdditionalInfo: map[string]any{
|
||||||
"release_mbid": expected,
|
"release_mbid": string(expected),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, track.ReleaseMbid())
|
assert.Equal(t, expected, track.ReleaseMBID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleaseGroupMbid(t *testing.T) {
|
func TestReleaseGroupMBID(t *testing.T) {
|
||||||
expected := "e02cc1c3-93fd-4e24-8b77-325060de920b"
|
expected := mbtypes.MBID("e02cc1c3-93fd-4e24-8b77-325060de920b")
|
||||||
track := listenbrainz.Track{
|
track := listenbrainz.Track{
|
||||||
AdditionalInfo: map[string]any{
|
AdditionalInfo: map[string]any{
|
||||||
"release_group_mbid": expected,
|
"release_group_mbid": string(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)
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult,
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(response.String())
|
err = errors.New(response.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ func (c Client) NewScrobble(scrobble NewScrobble) (result NewScrobbleResult, err
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
Post(path)
|
Post(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(response.String())
|
err = errors.New(response.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uploadedlobster.com/mbtypes"
|
||||||
"go.uploadedlobster.com/scotty/internal/models"
|
"go.uploadedlobster.com/scotty/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,7 +58,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
|
||||||
|
@ -100,7 +101,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"
|
||||||
|
@ -113,7 +114,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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +204,7 @@ func rowToListen(row []string, client string) (models.Listen, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(row) > 7 {
|
if len(row) > 7 {
|
||||||
listen.Track.RecordingMbid = models.MBID(row[7])
|
listen.Track.RecordingMBID = mbtypes.MBID(row[7])
|
||||||
}
|
}
|
||||||
|
|
||||||
return listen, nil
|
return listen, nil
|
||||||
|
|
|
@ -30,6 +30,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
@ -60,10 +61,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(models.MBID(""), listen1.RecordingMbid)
|
assert.Equal(mbtypes.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(models.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMbid)
|
assert.Equal(mbtypes.MBID("385ba9e9-626d-4750-a607-58e541dca78e"), listen4.RecordingMBID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParserExcludeSkipped(t *testing.T) {
|
func TestParserExcludeSkipped(t *testing.T) {
|
||||||
|
@ -74,7 +75,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(models.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMbid)
|
assert.Equal(mbtypes.MBID("1262beaf-19f8-4534-b9ed-7eef9ca8e83f"), listen4.RecordingMBID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWrite(t *testing.T) {
|
func TestWrite(t *testing.T) {
|
||||||
|
@ -93,7 +94,7 @@ func TestWrite(t *testing.T) {
|
||||||
TrackName: "Reign",
|
TrackName: "Reign",
|
||||||
TrackNumber: 1,
|
TrackNumber: 1,
|
||||||
Duration: 271 * time.Second,
|
Duration: 271 * time.Second,
|
||||||
RecordingMbid: models.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"),
|
RecordingMBID: mbtypes.MBID("b59cf4e7-caee-4019-a844-79d2c58d4dff"),
|
||||||
AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"},
|
AdditionalInfo: models.AdditionalInfo{"rockbox_rating": "L"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) (
|
||||||
}
|
}
|
||||||
response, err := request.Get(path)
|
response, err := request.Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(response.String())
|
err = errors.New(response.String())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -95,7 +95,7 @@ func (c Client) UserTracks(offset int, limit int) (result TracksResult, err erro
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
if response.StatusCode() != 200 {
|
if !response.IsSuccess() {
|
||||||
err = errors.New(response.String())
|
err = errors.New(response.String())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -22,6 +22,8 @@ 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"`
|
||||||
|
@ -98,7 +100,7 @@ type Artist struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExternalIds struct {
|
type ExternalIds struct {
|
||||||
ISRC string `json:"isrc"`
|
ISRC mbtypes.ISRC `json:"isrc"`
|
||||||
EAN string `json:"ean"`
|
EAN string `json:"ean"`
|
||||||
UPC string `json:"upc"`
|
UPC string `json:"upc"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"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"
|
||||||
"go.uploadedlobster.com/scotty/internal/util"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/spotify"
|
"golang.org/x/oauth2/spotify"
|
||||||
)
|
)
|
||||||
|
@ -184,7 +183,7 @@ out:
|
||||||
if offset >= result.Total {
|
if offset >= result.Total {
|
||||||
p.Total = int64(result.Total)
|
p.Total = int64(result.Total)
|
||||||
totalCount = result.Total
|
totalCount = result.Total
|
||||||
offset = util.Max(result.Total-perPage, 0)
|
offset = max(result.Total-perPage, 0)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
@ -59,7 +60,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, "DES561620801", listen.ISRC)
|
assert.Equal(t, mbtypes.ISRC("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"])
|
||||||
|
|
|
@ -105,7 +105,9 @@ func SongAsLove(song subsonic.Child, username string) models.Love {
|
||||||
ArtistNames: []string{song.Artist},
|
ArtistNames: []string{song.Artist},
|
||||||
TrackNumber: song.Track,
|
TrackNumber: song.Track,
|
||||||
DiscNumber: song.DiscNumber,
|
DiscNumber: song.DiscNumber,
|
||||||
AdditionalInfo: map[string]any{},
|
AdditionalInfo: map[string]any{
|
||||||
|
"subsonic_id": song.ID,
|
||||||
|
},
|
||||||
Duration: time.Duration(song.Duration * int(time.Second)),
|
Duration: time.Duration(song.Duration * int(time.Second)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ func TestFromConfig(t *testing.T) {
|
||||||
func TestSongToLove(t *testing.T) {
|
func TestSongToLove(t *testing.T) {
|
||||||
user := "outsidecontext"
|
user := "outsidecontext"
|
||||||
song := go_subsonic.Child{
|
song := go_subsonic.Child{
|
||||||
|
ID: "foo123",
|
||||||
Starred: time.Unix(1699574369, 0),
|
Starred: time.Unix(1699574369, 0),
|
||||||
Title: "Oweynagat",
|
Title: "Oweynagat",
|
||||||
Album: "Here Now, There Then",
|
Album: "Here Now, There Then",
|
||||||
|
@ -59,4 +60,5 @@ func TestSongToLove(t *testing.T) {
|
||||||
assert.Equal(song.Track, love.Track.TrackNumber)
|
assert.Equal(song.Track, love.Track.TrackNumber)
|
||||||
assert.Equal(song.DiscNumber, love.Track.DiscNumber)
|
assert.Equal(song.DiscNumber, love.Track.DiscNumber)
|
||||||
assert.Equal([]string{song.Genre}, love.Track.Tags)
|
assert.Equal([]string{song.Genre}, love.Track.Tags)
|
||||||
|
assert.Equal(song.ID, love.AdditionalInfo["subsonic_id"])
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,10 @@ package models
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uploadedlobster.com/mbtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MBID string
|
|
||||||
type Entity string
|
type Entity string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -43,12 +44,12 @@ type Track struct {
|
||||||
TrackNumber int
|
TrackNumber int
|
||||||
DiscNumber int
|
DiscNumber int
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
ISRC string
|
ISRC mbtypes.ISRC
|
||||||
RecordingMbid MBID
|
RecordingMBID mbtypes.MBID
|
||||||
ReleaseMbid MBID
|
ReleaseMBID mbtypes.MBID
|
||||||
ReleaseGroupMbid MBID
|
ReleaseGroupMBID mbtypes.MBID
|
||||||
ArtistMbids []MBID
|
ArtistMBIDs []mbtypes.MBID
|
||||||
WorkMbids []MBID
|
WorkMBIDs []mbtypes.MBID
|
||||||
Tags []string
|
Tags []string
|
||||||
AdditionalInfo AdditionalInfo
|
AdditionalInfo AdditionalInfo
|
||||||
}
|
}
|
||||||
|
@ -62,20 +63,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
|
||||||
|
@ -110,8 +111,8 @@ type Love struct {
|
||||||
Track
|
Track
|
||||||
Created time.Time
|
Created time.Time
|
||||||
UserName string
|
UserName string
|
||||||
RecordingMbid MBID
|
RecordingMBID mbtypes.MBID
|
||||||
RecordingMsid MBID
|
RecordingMsid mbtypes.MBID
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListensList []Listen
|
type ListensList []Listen
|
||||||
|
|
|
@ -28,6 +28,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,25 +45,25 @@ func TestTrackArtistName(t *testing.T) {
|
||||||
|
|
||||||
func TestTrackFillAdditionalInfo(t *testing.T) {
|
func TestTrackFillAdditionalInfo(t *testing.T) {
|
||||||
track := models.Track{
|
track := models.Track{
|
||||||
RecordingMbid: models.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"),
|
RecordingMBID: mbtypes.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"),
|
||||||
ReleaseGroupMbid: models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"),
|
ReleaseGroupMBID: mbtypes.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"),
|
||||||
ReleaseMbid: models.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"),
|
ReleaseMBID: mbtypes.MBID("aa1ea1ac-7ec4-4542-a494-105afbfe547d"),
|
||||||
ArtistMbids: []models.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"},
|
ArtistMBIDs: []mbtypes.MBID{"24412926-c7bd-48e8-afad-8a285b42e131"},
|
||||||
WorkMbids: []models.MBID{"c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"},
|
WorkMBIDs: []mbtypes.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: "DES561620801",
|
ISRC: mbtypes.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"])
|
||||||
|
|
|
@ -33,7 +33,7 @@ func Similarity(s1 string, s2 string) float64 {
|
||||||
s2 = norm.NFKC.String(s2)
|
s2 = norm.NFKC.String(s2)
|
||||||
l1 := len([]rune(s1))
|
l1 := len([]rune(s1))
|
||||||
l2 := len([]rune(s2))
|
l2 := len([]rune(s2))
|
||||||
maxLen := util.Max(l1, l2)
|
maxLen := max(l1, l2)
|
||||||
// Empty strings always compare full equal
|
// Empty strings always compare full equal
|
||||||
if maxLen == 0 {
|
if maxLen == 0 {
|
||||||
return 1.0
|
return 1.0
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ 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"
|
||||||
)
|
)
|
||||||
|
@ -74,13 +75,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: models.MBID("2886d15c-09b0-43c6-af56-932f70dde164"),
|
RecordingMBID: mbtypes.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: models.MBID("2886d15c-09b0-43c6-af56-932f70dde164"),
|
RecordingMBID: mbtypes.MBID("2886d15c-09b0-43c6-af56-932f70dde164"),
|
||||||
}
|
}
|
||||||
assert.Equal(t, 1.0, similarity.CompareTracks(t1, t2))
|
assert.Equal(t, 1.0, similarity.CompareTracks(t1, t2))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": 52,
|
"%v: %v": 47,
|
||||||
"Aborted": 8,
|
"Aborted": 8,
|
||||||
"Access token": 19,
|
"Access token": 19,
|
||||||
"Access token received, you can use %v now.\n": 28,
|
"Access token received, you can use %v now.\n": 33,
|
||||||
"Append to file": 21,
|
"Append to file": 21,
|
||||||
"Backend": 36,
|
"Backend": 41,
|
||||||
"Check for duplicate listens on import (slower)": 45,
|
"Check for duplicate listens on import (slower)": 24,
|
||||||
"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": 47,
|
"Directory path": 27,
|
||||||
"Disable auto correction of submitted listens": 24,
|
"Disable auto correction of submitted listens": 25,
|
||||||
"Error: OAuth state mismatch": 27,
|
"Error: OAuth state mismatch": 32,
|
||||||
"Failed reading config: %v": 2,
|
"Failed reading config: %v": 2,
|
||||||
"File path": 20,
|
"File path": 20,
|
||||||
"From timestamp: %v (%v)": 38,
|
"From timestamp: %v (%v)": 43,
|
||||||
"Ignore listens in incognito mode": 48,
|
"Ignore listens in incognito mode": 28,
|
||||||
"Ignore skipped listens": 49,
|
"Ignore skipped listens": 29,
|
||||||
"Ignored duplicate listen %v: \"%v\" by %v (%v)": 46,
|
"Ignored duplicate listen %v: \"%v\" by %v (%v)": 53,
|
||||||
"Import failed, last reported timestamp was %v (%s)": 39,
|
"Import failed, last reported timestamp was %v (%s)": 44,
|
||||||
"Import log:": 51,
|
"Import log:": 46,
|
||||||
"Imported %v of %v %s into %v.": 40,
|
"Imported %v of %v %s into %v.": 45,
|
||||||
"Include skipped listens": 25,
|
"Include skipped listens": 26,
|
||||||
"Latest timestamp: %v (%v)": 41,
|
"Latest timestamp: %v (%v)": 49,
|
||||||
"Minimum playback duration for skipped tracks (seconds)": 50,
|
"Minimum playback duration for skipped tracks (seconds)": 30,
|
||||||
"No": 33,
|
"No": 38,
|
||||||
"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": 35,
|
"Service": 40,
|
||||||
"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...": 37,
|
"Transferring %s from %s to %s...": 42,
|
||||||
"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": 26,
|
"Visit the URL for authorization: %v": 31,
|
||||||
"Yes": 32,
|
"Yes": 37,
|
||||||
"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": 31,
|
"done": 36,
|
||||||
"exporting": 29,
|
"exporting": 34,
|
||||||
"importing": 30,
|
"importing": 35,
|
||||||
"invalid timestamp string \"%v\"": 53,
|
"invalid timestamp string \"%v\"": 48,
|
||||||
"key must only consist of A-Za-z0-9_-": 43,
|
"key must only consist of A-Za-z0-9_-": 51,
|
||||||
"no configuration file defined, cannot write config": 42,
|
"no configuration file defined, cannot write config": 50,
|
||||||
"no existing service configurations": 34,
|
"no existing service configurations": 39,
|
||||||
"no service configuration \"%v\"": 44,
|
"no service configuration \"%v\"": 52,
|
||||||
"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, 0x000002ec, 0x0000030d, 0x00000333,
|
0x000002b8, 0x000002ed, 0x00000321, 0x00000342,
|
||||||
0x0000035d, 0x0000039d, 0x000003a8, 0x000003b3,
|
0x00000352, 0x00000378, 0x0000039a, 0x000003d8,
|
||||||
// Entry 20 - 3F
|
// Entry 20 - 3F
|
||||||
0x000003ba, 0x000003bd, 0x000003c2, 0x000003eb,
|
0x000003fe, 0x00000428, 0x00000468, 0x00000473,
|
||||||
0x000003f3, 0x000003fb, 0x00000424, 0x00000442,
|
0x0000047e, 0x00000485, 0x00000488, 0x0000048d,
|
||||||
0x0000047f, 0x000004aa, 0x000004cd, 0x0000051e,
|
0x000004b6, 0x000004be, 0x000004c6, 0x000004ef,
|
||||||
0x00000555, 0x0000057c, 0x0000057c, 0x0000057c,
|
0x0000050d, 0x0000054a, 0x00000575, 0x00000580,
|
||||||
0x0000057c, 0x0000057c, 0x0000057c, 0x0000057c,
|
0x0000058d, 0x000005b1, 0x000005d4, 0x00000625,
|
||||||
0x0000057c, 0x0000057c, 0x0000057c,
|
0x0000065c, 0x00000683, 0x00000683,
|
||||||
} // Size: 244 bytes
|
} // Size: 244 bytes
|
||||||
|
|
||||||
const deData string = "" + // Size: 1404 bytes
|
const deData string = "" + // Size: 1667 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,18 +126,22 @@ const deData string = "" + // Size: 1404 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\x02Autokorrektur für übermit" +
|
"itel der Playlist\x02Eindeutige Playlist-ID\x02Beim Import auf Listen-Du" +
|
||||||
"telte Titel deaktivieren\x02Übersprungene Titel einbeziehen\x02URL für A" +
|
"plikate prüfen (langsamer)\x02Autokorrektur für übermittelte Titel deakt" +
|
||||||
"utorisierung öffnen: %[1]v\x02Fehler: OAuth-State stimmt nicht überein" +
|
"ivieren\x02Übersprungene Titel einbeziehen\x02Verzeichnispfad\x02Listens" +
|
||||||
"\x04\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet " +
|
" im Inkognito-Modus ignorieren\x02Übersprungene Listens ignorieren\x02Mi" +
|
||||||
"werden.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bes" +
|
"nimale Wiedergabedauer für übersprungene Titel (Sekunden)\x02URL für Aut" +
|
||||||
"tehenden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s" +
|
"orisierung öffnen: %[1]v\x02Fehler: OAuth-State stimmt nicht überein\x04" +
|
||||||
" von %[2]s nach %[3]s...\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehl" +
|
"\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwendet werd" +
|
||||||
"geschlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3" +
|
"en.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine bestehe" +
|
||||||
"]s in %[4]v importiert.\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine K" +
|
"nden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %[1]s von" +
|
||||||
"onfigurationsdatei definiert, Konfiguration kann nicht geschrieben werde" +
|
" %[2]s nach %[3]s...\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fehlgesc" +
|
||||||
"n\x02Schlüssel darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Serv" +
|
"hlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %[3]s i" +
|
||||||
"icekonfiguration „%[1]v“"
|
"n %[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Zeitstem" +
|
||||||
|
"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
|
||||||
|
@ -147,15 +151,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, 0x00000288, 0x000002a0, 0x000002c7,
|
0x0000025b, 0x0000028a, 0x000002b7, 0x000002cf,
|
||||||
0x000002e3, 0x00000316, 0x00000320, 0x0000032a,
|
0x000002de, 0x000002ff, 0x00000316, 0x0000034d,
|
||||||
// Entry 20 - 3F
|
// Entry 20 - 3F
|
||||||
0x0000032f, 0x00000333, 0x00000336, 0x00000359,
|
0x00000374, 0x00000390, 0x000003c3, 0x000003cd,
|
||||||
0x00000361, 0x00000369, 0x00000393, 0x000003b1,
|
0x000003d7, 0x000003dc, 0x000003e0, 0x000003e3,
|
||||||
0x000003ea, 0x00000414, 0x00000434, 0x00000467,
|
0x00000406, 0x0000040e, 0x00000416, 0x00000440,
|
||||||
0x0000048c, 0x000004ad, 0x000004dc, 0x00000515,
|
0x0000045e, 0x00000497, 0x000004c1, 0x000004cd,
|
||||||
0x00000524, 0x00000545, 0x0000055c, 0x00000593,
|
0x000004da, 0x000004fb, 0x0000051b, 0x0000054e,
|
||||||
0x0000059f, 0x000005ac, 0x000005cd,
|
0x00000573, 0x00000594, 0x000005cd,
|
||||||
} // Size: 244 bytes
|
} // Size: 244 bytes
|
||||||
|
|
||||||
const enData string = "" + // Size: 1485 bytes
|
const enData string = "" + // Size: 1485 bytes
|
||||||
|
@ -169,20 +173,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\x02Disable auto corre" +
|
"le\x02Playlist title\x02Unique playlist identifier\x02Check for duplicat" +
|
||||||
"ction of submitted listens\x02Include skipped listens\x02Visit the URL f" +
|
"e listens on import (slower)\x02Disable auto correction of submitted lis" +
|
||||||
"or authorization: %[1]v\x02Error: OAuth state mismatch\x04\x00\x01\x0a." +
|
"tens\x02Include skipped listens\x02Directory path\x02Ignore listens in i" +
|
||||||
"\x02Access token received, you can use %[1]v now.\x02exporting\x02import" +
|
"ncognito mode\x02Ignore skipped listens\x02Minimum playback duration for" +
|
||||||
"ing\x02done\x02Yes\x02No\x02no existing service configurations\x02Servic" +
|
" skipped tracks (seconds)\x02Visit the URL for authorization: %[1]v\x02E" +
|
||||||
"e\x02Backend\x02Transferring %[1]s from %[2]s to %[3]s...\x02From timest" +
|
"rror: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token received, yo" +
|
||||||
"amp: %[1]v (%[2]v)\x02Import failed, last reported timestamp was %[1]v (" +
|
"u can use %[1]v now.\x02exporting\x02importing\x02done\x02Yes\x02No\x02n" +
|
||||||
"%[2]s)\x02Imported %[1]v of %[2]v %[3]s into %[4]v.\x02Latest timestamp:" +
|
"o existing service configurations\x02Service\x02Backend\x02Transferring " +
|
||||||
" %[1]v (%[2]v)\x02no configuration file defined, cannot write config\x02" +
|
"%[1]s from %[2]s to %[3]s...\x02From timestamp: %[1]v (%[2]v)\x02Import " +
|
||||||
"key must only consist of A-Za-z0-9_-\x02no service configuration \x22%[1" +
|
"failed, last reported timestamp was %[1]v (%[2]s)\x02Imported %[1]v of %" +
|
||||||
"]v\x22\x02Check for duplicate listens on import (slower)\x02Ignored dupl" +
|
"[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v\x02invalid timesta" +
|
||||||
"icate listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)\x02Directory path\x02" +
|
"mp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%[2]v)\x02no configu" +
|
||||||
"Ignore listens in incognito mode\x02Ignore skipped listens\x02Minimum pl" +
|
"ration file defined, cannot write config\x02key must only consist of A-Z" +
|
||||||
"ayback duration for skipped tracks (seconds)\x02Import log:\x02%[1]v: %[" +
|
"a-z0-9_-\x02no service configuration \x22%[1]v\x22\x02Ignored duplicate " +
|
||||||
"2]v\x02invalid timestamp string \x22%[1]v\x22"
|
"listen %[1]v: \x22%[2]v\x22 by %[3]v (%[4]v)"
|
||||||
|
|
||||||
// Total table size 3377 bytes (3KiB); checksum: 6715024
|
// Total table size 3640 bytes (3KiB); checksum: 719A868A
|
||||||
|
|
|
@ -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": ""
|
"translation": "Beim Import auf Listen-Duplikate prüfen (langsamer)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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/scotty/internal/models.MBID",
|
"type": "go.uploadedlobster.com/mbtypes.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": ""
|
"translation": "Verzeichnispfad"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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": ""
|
"translation": "Listens im Inkognito-Modus ignorieren"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ignore skipped listens",
|
"id": "Ignore skipped listens",
|
||||||
"message": "Ignore skipped listens",
|
"message": "Ignore skipped listens",
|
||||||
"translation": ""
|
"translation": "Übersprungene Listens ignorieren"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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": ""
|
"translation": "Minimale Wiedergabedauer für übersprungene Titel (Sekunden)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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": ""
|
"translation": "Importlog:"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "{Type}: {Message}",
|
"id": "{Type}: {Message}",
|
||||||
"message": "{Type}: {Message}",
|
"message": "{Type}: {Message}",
|
||||||
"translation": "",
|
"translation": "{Type}: {Message}",
|
||||||
"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": "",
|
"translation": "ungültiger Zeitstempel „{FlagValue}“",
|
||||||
"placeholders": [
|
"placeholders": [
|
||||||
{
|
{
|
||||||
"id": "FlagValue",
|
"id": "FlagValue",
|
||||||
|
|
|
@ -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/scotty/internal/models.MBID",
|
"type": "go.uploadedlobster.com/mbtypes.MBID",
|
||||||
"underlyingType": "string",
|
"underlyingType": "string",
|
||||||
"argNum": 4,
|
"argNum": 4,
|
||||||
"expr": "l.RecordingMbid"
|
"expr": "l.RecordingMBID"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"fuzzy": true
|
"fuzzy": true
|
||||||
|
|
|
@ -6,4 +6,4 @@ package are published under the conditions of CC0 1.0 Universal (CC0 1.0)
|
||||||
|
|
||||||
package translations
|
package translations
|
||||||
|
|
||||||
//go:generate gotext -srclang=en update -out=catalog.go -lang=en,de go.uploadedlobster.com/scotty
|
//go:generate go tool gotext -srclang=en update -out=catalog.go -lang=en,de go.uploadedlobster.com/scotty
|
||||||
|
|
|
@ -17,22 +17,6 @@ package util
|
||||||
|
|
||||||
import "golang.org/x/exp/constraints"
|
import "golang.org/x/exp/constraints"
|
||||||
|
|
||||||
func Max[T constraints.Ordered](m, n T) T {
|
|
||||||
if n > m {
|
|
||||||
return n
|
|
||||||
} else {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Min[T constraints.Ordered](m, n T) T {
|
|
||||||
if n < m {
|
|
||||||
return n
|
|
||||||
} else {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sum[T constraints.Integer | constraints.Float](v ...T) T {
|
func Sum[T constraints.Integer | constraints.Float](v ...T) T {
|
||||||
var sum T
|
var sum T
|
||||||
for _, i := range v {
|
for _, i := range v {
|
||||||
|
|
|
@ -23,18 +23,6 @@ import (
|
||||||
"go.uploadedlobster.com/scotty/internal/util"
|
"go.uploadedlobster.com/scotty/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleMax() {
|
|
||||||
v := util.Max(2, 5)
|
|
||||||
fmt.Print(v)
|
|
||||||
// Output: 5
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleMin() {
|
|
||||||
v := util.Min(2, 5)
|
|
||||||
fmt.Print(v)
|
|
||||||
// Output: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleSum() {
|
func ExampleSum() {
|
||||||
values := []float64{1.4, 2.2}
|
values := []float64{1.4, 2.2}
|
||||||
sum := util.Sum(values...)
|
sum := util.Sum(values...)
|
||||||
|
|
|
@ -17,7 +17,7 @@ package version
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AppName = "scotty"
|
AppName = "scotty"
|
||||||
AppVersion = "0.4.0"
|
AppVersion = "0.4.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UserAgent() string {
|
func UserAgent() string {
|
||||||
|
|
Loading…
Add table
Reference in a new issue