mirror of
https://github.com/navidrome/navidrome.git
synced 2025-07-14 07:31:28 +03:00
* Add prometheus metrics to subsonic and plugins * address feedback, do not log error if operation is not supported * add missing timestamp and client to stats * remove .view from subsonic route * directly inject DataStore in Prometheus, to avoid having to pass it in every call Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org> Co-authored-by: Deluan <deluan@navidrome.org>
155 lines
4.4 KiB
Go
155 lines
4.4 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/core/scrobbler"
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/model/request"
|
|
"github.com/navidrome/navidrome/plugins/api"
|
|
"github.com/tetratelabs/wazero"
|
|
)
|
|
|
|
func newWasmScrobblerPlugin(wasmPath, pluginID string, m *Manager, runtime api.WazeroNewRuntime, mc wazero.ModuleConfig) WasmPlugin {
|
|
loader, err := api.NewScrobblerPlugin(context.Background(), api.WazeroRuntime(runtime), api.WazeroModuleConfig(mc))
|
|
if err != nil {
|
|
log.Error("Error creating scrobbler service plugin", "plugin", pluginID, "path", wasmPath, err)
|
|
return nil
|
|
}
|
|
return &wasmScrobblerPlugin{
|
|
wasmBasePlugin: newWasmBasePlugin[api.Scrobbler, *api.ScrobblerPlugin](
|
|
wasmPath,
|
|
pluginID,
|
|
CapabilityScrobbler,
|
|
m.metrics,
|
|
loader,
|
|
func(ctx context.Context, l *api.ScrobblerPlugin, path string) (api.Scrobbler, error) {
|
|
return l.Load(ctx, path)
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
type wasmScrobblerPlugin struct {
|
|
*wasmBasePlugin[api.Scrobbler, *api.ScrobblerPlugin]
|
|
}
|
|
|
|
func (w *wasmScrobblerPlugin) IsAuthorized(ctx context.Context, userId string) bool {
|
|
username, _ := request.UsernameFrom(ctx)
|
|
if username == "" {
|
|
u, ok := request.UserFrom(ctx)
|
|
if ok {
|
|
username = u.UserName
|
|
}
|
|
}
|
|
|
|
result, err := callMethod(ctx, w, "IsAuthorized", func(inst api.Scrobbler) (bool, error) {
|
|
resp, err := inst.IsAuthorized(ctx, &api.ScrobblerIsAuthorizedRequest{
|
|
UserId: userId,
|
|
Username: username,
|
|
})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if resp.Error != "" {
|
|
return false, nil
|
|
}
|
|
return resp.Authorized, nil
|
|
})
|
|
return err == nil && result
|
|
}
|
|
|
|
func (w *wasmScrobblerPlugin) NowPlaying(ctx context.Context, userId string, track *model.MediaFile, position int) error {
|
|
username, _ := request.UsernameFrom(ctx)
|
|
if username == "" {
|
|
u, ok := request.UserFrom(ctx)
|
|
if ok {
|
|
username = u.UserName
|
|
}
|
|
}
|
|
|
|
artists := make([]*api.Artist, 0, len(track.Participants[model.RoleArtist]))
|
|
for _, a := range track.Participants[model.RoleArtist] {
|
|
artists = append(artists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
|
|
}
|
|
albumArtists := make([]*api.Artist, 0, len(track.Participants[model.RoleAlbumArtist]))
|
|
for _, a := range track.Participants[model.RoleAlbumArtist] {
|
|
albumArtists = append(albumArtists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
|
|
}
|
|
trackInfo := &api.TrackInfo{
|
|
Id: track.ID,
|
|
Mbid: track.MbzRecordingID,
|
|
Name: track.Title,
|
|
Album: track.Album,
|
|
AlbumMbid: track.MbzAlbumID,
|
|
Artists: artists,
|
|
AlbumArtists: albumArtists,
|
|
Length: int32(track.Duration),
|
|
Position: int32(position),
|
|
}
|
|
_, err := callMethod(ctx, w, "NowPlaying", func(inst api.Scrobbler) (struct{}, error) {
|
|
resp, err := inst.NowPlaying(ctx, &api.ScrobblerNowPlayingRequest{
|
|
UserId: userId,
|
|
Username: username,
|
|
Track: trackInfo,
|
|
Timestamp: time.Now().Unix(),
|
|
})
|
|
if err != nil {
|
|
return struct{}{}, err
|
|
}
|
|
if resp.Error != "" {
|
|
return struct{}{}, nil
|
|
}
|
|
return struct{}{}, nil
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (w *wasmScrobblerPlugin) Scrobble(ctx context.Context, userId string, s scrobbler.Scrobble) error {
|
|
username, _ := request.UsernameFrom(ctx)
|
|
if username == "" {
|
|
u, ok := request.UserFrom(ctx)
|
|
if ok {
|
|
username = u.UserName
|
|
}
|
|
}
|
|
|
|
track := &s.MediaFile
|
|
artists := make([]*api.Artist, 0, len(track.Participants[model.RoleArtist]))
|
|
for _, a := range track.Participants[model.RoleArtist] {
|
|
artists = append(artists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
|
|
}
|
|
albumArtists := make([]*api.Artist, 0, len(track.Participants[model.RoleAlbumArtist]))
|
|
for _, a := range track.Participants[model.RoleAlbumArtist] {
|
|
albumArtists = append(albumArtists, &api.Artist{Name: a.Name, Mbid: a.MbzArtistID})
|
|
}
|
|
trackInfo := &api.TrackInfo{
|
|
Id: track.ID,
|
|
Mbid: track.MbzRecordingID,
|
|
Name: track.Title,
|
|
Album: track.Album,
|
|
AlbumMbid: track.MbzAlbumID,
|
|
Artists: artists,
|
|
AlbumArtists: albumArtists,
|
|
Length: int32(track.Duration),
|
|
}
|
|
_, err := callMethod(ctx, w, "Scrobble", func(inst api.Scrobbler) (struct{}, error) {
|
|
resp, err := inst.Scrobble(ctx, &api.ScrobblerScrobbleRequest{
|
|
UserId: userId,
|
|
Username: username,
|
|
Track: trackInfo,
|
|
Timestamp: s.TimeStamp.Unix(),
|
|
})
|
|
if err != nil {
|
|
return struct{}{}, err
|
|
}
|
|
if resp.Error != "" {
|
|
return struct{}{}, nil
|
|
}
|
|
return struct{}{}, nil
|
|
})
|
|
return err
|
|
}
|