Implemented Maloja listen export

This commit is contained in:
Philipp Wolfer 2023-11-10 00:00:31 +01:00
parent 10590f9f67
commit 0bee8561e3
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
6 changed files with 217 additions and 5 deletions

View file

@ -76,5 +76,6 @@ func ResolveBackend[T interface{}](config *viper.Viper) (T, error) {
var knownBackends = map[string]func() Backend{
"dump": func() Backend { return &DumpBackend{} },
"listenbrainz-api": func() Backend { return &ListenBrainzApiBackend{} },
"maloja-api": func() Backend { return &MalojaApiBackend{} },
"scrobbler-log": func() Backend { return &ScrobblerLogBackend{} },
}

View file

@ -55,11 +55,11 @@ func (t Track) Duration() time.Duration {
var duration time.Duration
milliseconds, ok := t.AdditionalInfo["duration_ms"].(float64)
if ok {
duration = time.Duration(int(milliseconds) * int(time.Millisecond))
duration = time.Duration(int64(milliseconds) * int64(time.Millisecond))
} else {
seconds, ok := t.AdditionalInfo["duration_ms"].(float64)
if ok {
duration = time.Duration(int(seconds) * int(time.Second))
duration = time.Duration(int64(seconds) * int64(time.Second))
}
}
return duration

97
backends/maloja.go Normal file
View file

@ -0,0 +1,97 @@
/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package backends
import (
"slices"
"strings"
"time"
"github.com/spf13/viper"
"go.uploadedlobster.com/scotty/backends/maloja"
)
type MalojaApiBackend struct {
client maloja.Client
}
func (b MalojaApiBackend) FromConfig(config *viper.Viper) Backend {
b.client = maloja.New(
config.GetString("server-url"),
config.GetString("token"),
)
return b
}
func (b MalojaApiBackend) ExportListens(oldestTimestamp time.Time) ([]Listen, error) {
page := 0
perPage := 1000
listens := make([]Listen, 0)
out:
for {
result, err := b.client.GetListens(page, perPage)
if err != nil {
return nil, err
}
count := len(result.Listens)
if count == 0 {
break
}
for _, listen := range result.Listens {
if listen.ListenedAt > oldestTimestamp.Unix() {
listens = append(listens, Listen{}.FromMaloja(listen))
} else {
break out
}
}
page += 1
}
slices.Reverse(listens)
return listens, nil
}
func (l Listen) FromMaloja(mlListen maloja.Listen) Listen {
track := mlListen.Track
listen := Listen{
ListenedAt: time.Unix(mlListen.ListenedAt, 0),
PlaybackDuration: time.Duration(mlListen.Duration * int64(time.Second)),
Track: Track{
TrackName: track.Title,
ReleaseName: track.Album.Title,
ArtistNames: track.Artists,
Duration: time.Duration(track.Length * int64(time.Second)),
},
}
client, found := strings.CutPrefix(mlListen.Origin, "client:")
if found {
listen.AdditionalInfo["media_player"] = client
}
return listen
}

58
backends/maloja/client.go Normal file
View file

@ -0,0 +1,58 @@
/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package maloja
import (
"strconv"
"github.com/go-resty/resty/v2"
)
type Client struct {
resty *resty.Client
token string
}
func New(serverUrl string, token string) Client {
resty := resty.New()
resty.SetBaseURL(serverUrl)
resty.SetHeader("Accept", "application/json")
client := Client{
resty: resty,
token: token,
}
return client
}
func (c Client) GetListens(page int, perPage int) (GetListensResult, error) {
const path = "/apis/mlj_1/scrobbles"
result := &GetListensResult{}
_, err := c.resty.R().
SetQueryParams(map[string]string{
"page": strconv.Itoa(page),
"perpage": strconv.Itoa(perPage),
}).
SetResult(result).
Get(path)
return *result, err
}

56
backends/maloja/models.go Normal file
View file

@ -0,0 +1,56 @@
/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package maloja
type GetListensResult struct {
Status string `json:"status"`
Listens []Listen `json:"list"`
Pagination Pagination `json:"pagination"`
}
type Listen struct {
ListenedAt int64 `json:"time"`
Duration int64 `json:"duration"`
// Maloja sets Origin to the name of the API key
// prefixed with "client:"
Origin string `json:"origin"`
Track Track `json:"track"`
}
type Track struct {
Title string `json:"title"`
Artists []string `json:"artists"`
Album Album `json:"album"`
Length int64 `json:"length"`
}
type Album struct {
Title string `json:"albumtitle"`
Artists []string `json:"artists"`
}
type Pagination struct {
Page int `json:"page"`
PerPage int `json:"perpage"`
NextPage string `json:"next_page"`
PrevPage string `json:"prev_page"`
}

View file

@ -52,7 +52,7 @@ func (t Track) ArtistName() string {
type Listen struct {
Track
ListenedAt time.Time
ListenDuration time.Duration
UserName string
ListenedAt time.Time
PlaybackDuration time.Duration
UserName string
}