112 lines
4.0 KiB
Go
112 lines
4.0 KiB
Go
package embyapi
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/bjarneo/cliamp/internal/appmeta"
|
|
"github.com/bjarneo/cliamp/provider"
|
|
)
|
|
|
|
// dialect captures the handful of behaviors that differ between Emby and
|
|
// Jellyfin. Everything else in Client is shared.
|
|
type dialect interface {
|
|
name() string // error-wrapping prefix
|
|
pingPath() string // endpoint Ping hits
|
|
metaKey() string // playlist.Track ProviderMeta key
|
|
applyAuth(req *http.Request, token, userID, deviceID string) // set auth headers
|
|
discoverUserID(c *Client) (string, error) // user-id discovery strategy
|
|
}
|
|
|
|
// embyDialect speaks Emby's `Authorization: Emby ...` scheme and discovers the
|
|
// user id with an API-key fallback.
|
|
type embyDialect struct{}
|
|
|
|
func (embyDialect) name() string { return "emby" }
|
|
func (embyDialect) pingPath() string { return "/System/Info" }
|
|
func (embyDialect) metaKey() string { return provider.MetaEmbyID }
|
|
|
|
func (embyDialect) applyAuth(req *http.Request, token, userID, deviceID string) {
|
|
if token != "" {
|
|
req.Header.Set("X-Emby-Token", token)
|
|
req.Header.Set("Authorization", embyAuthHeader(userID, token, deviceID))
|
|
} else {
|
|
req.Header.Set("Authorization", embyUnauthHeader(deviceID))
|
|
}
|
|
}
|
|
|
|
func (embyDialect) discoverUserID(c *Client) (string, error) {
|
|
// Try /Users/Me first (works for session tokens from password auth).
|
|
var me userDTO
|
|
if err := c.get("/Users/Me", nil, &me); err == nil && me.ID != "" {
|
|
c.setUserID(me.ID)
|
|
return me.ID, nil
|
|
}
|
|
|
|
// Fall back to /Users for API key auth (server-level key has no "me").
|
|
var users []userDTO
|
|
if err := c.get("/Users", nil, &users); err != nil {
|
|
return "", fmt.Errorf("emby: could not discover user id (set user_id in config): %w", err)
|
|
}
|
|
// Prefer user matching the configured username; otherwise take first entry.
|
|
for _, u := range users {
|
|
if strings.EqualFold(u.Name, c.user) {
|
|
c.setUserID(u.ID)
|
|
return u.ID, nil
|
|
}
|
|
}
|
|
if c.user != "" {
|
|
return "", fmt.Errorf("emby: user %q not found — check the user name in config", c.user)
|
|
}
|
|
if len(users) > 0 && users[0].ID != "" {
|
|
c.setUserID(users[0].ID)
|
|
return users[0].ID, nil
|
|
}
|
|
return "", fmt.Errorf("emby: could not discover user id — set user_id in config")
|
|
}
|
|
|
|
// unauthHeader / authHeader build Emby's Authorization header values.
|
|
func embyUnauthHeader(deviceID string) string {
|
|
return fmt.Sprintf(`Emby Client="%s", Device="%s", DeviceId="%s", Version="%s"`,
|
|
appmeta.ClientName(), appmeta.DeviceName(), deviceID, appmeta.Version())
|
|
}
|
|
|
|
func embyAuthHeader(userID, token, deviceID string) string {
|
|
if userID != "" {
|
|
return fmt.Sprintf(`Emby UserId="%s", Client="%s", Device="%s", DeviceId="%s", Version="%s", Token="%s"`,
|
|
userID, appmeta.ClientName(), appmeta.DeviceName(), deviceID, appmeta.Version(), token)
|
|
}
|
|
return fmt.Sprintf(`Emby Client="%s", Device="%s", DeviceId="%s", Version="%s", Token="%s"`,
|
|
appmeta.ClientName(), appmeta.DeviceName(), deviceID, appmeta.Version(), token)
|
|
}
|
|
|
|
// jellyfinDialect speaks Jellyfin's `X-Emby-Authorization: MediaBrowser ...`
|
|
// scheme and discovers the user id from /Users/Me only.
|
|
type jellyfinDialect struct{}
|
|
|
|
func (jellyfinDialect) name() string { return "jellyfin" }
|
|
func (jellyfinDialect) pingPath() string { return "/Users/Me" }
|
|
func (jellyfinDialect) metaKey() string { return provider.MetaJellyfinID }
|
|
|
|
func (jellyfinDialect) applyAuth(req *http.Request, token, _, deviceID string) {
|
|
if token != "" {
|
|
req.Header.Set("X-Emby-Token", token)
|
|
}
|
|
req.Header.Set("X-Emby-Authorization",
|
|
fmt.Sprintf(`MediaBrowser Client="%s", Device="%s", DeviceId="%s", Version="%s"`,
|
|
appmeta.ClientName(), appmeta.DeviceName(), deviceID, appmeta.Version()))
|
|
}
|
|
|
|
func (jellyfinDialect) discoverUserID(c *Client) (string, error) {
|
|
var u userDTO
|
|
if err := c.get("/Users/Me", nil, &u); err != nil {
|
|
return "", err
|
|
}
|
|
if u.ID == "" {
|
|
return "", fmt.Errorf("jellyfin: current user response missing id")
|
|
}
|
|
c.setUserID(u.ID)
|
|
return u.ID, nil
|
|
}
|