Expose session/last-used user and device IP on player rows. Filter retrieve_player_list by users, merge GET /Devices when include_offline is set, and resolve spoken names to HA media_player entities via player_links (device_id or IP). PlayNow still requires a live session.
160 lines
4.4 KiB
Go
160 lines
4.4 KiB
Go
package server
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"git.i3omb.com/gronod/emby-mcp/internal/config"
|
|
"git.i3omb.com/gronod/emby-mcp/internal/emby"
|
|
)
|
|
|
|
// ResolvedPlayer is the output of resolve_media_player.
|
|
type ResolvedPlayer struct {
|
|
Query string `json:"query"`
|
|
MatchedName string `json:"matched_name,omitempty"`
|
|
HAEntity string `json:"ha_entity,omitempty"`
|
|
HASource string `json:"ha_source,omitempty"`
|
|
EmbyDeviceID string `json:"emby_device_id,omitempty"`
|
|
EmbyDeviceName string `json:"emby_device_name,omitempty"`
|
|
DeviceIP string `json:"device_ip,omitempty"`
|
|
SessionID string `json:"session_id,omitempty"`
|
|
Online bool `json:"online"`
|
|
UserName string `json:"user_name,omitempty"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
WakeRequired bool `json:"wake_required"`
|
|
Note string `json:"note,omitempty"`
|
|
}
|
|
|
|
func resolvePlayer(query string, links []config.PlayerLink, players []emby.PlayerSession) (ResolvedPlayer, bool) {
|
|
q := strings.TrimSpace(query)
|
|
out := ResolvedPlayer{Query: q}
|
|
if q == "" {
|
|
out.Note = "ERROR: no query was supplied"
|
|
return out, false
|
|
}
|
|
ql := strings.ToLower(q)
|
|
|
|
if link, name := matchLink(ql, links); link != nil {
|
|
out.MatchedName = name
|
|
out.HAEntity = link.HAEntity
|
|
out.HASource = link.HASource
|
|
out.EmbyDeviceID = link.EmbyDeviceID
|
|
out.EmbyDeviceName = link.EmbyDeviceName
|
|
out.DeviceIP = emby.HostOf(link.DeviceIP)
|
|
if sess, ok := matchSession(link, players); ok {
|
|
fillFromSession(&out, sess)
|
|
} else {
|
|
out.WakeRequired = true
|
|
out.Note = "No live Emby session for this device. Turn on the Home Assistant media_player and launch the Emby client, then list or resolve again."
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
for _, p := range players {
|
|
if sessionMatchesQuery(ql, p) {
|
|
out.MatchedName = firstNonEmpty(p.DeviceName, p.ClientName)
|
|
fillFromSession(&out, p)
|
|
if !p.Online {
|
|
out.WakeRequired = true
|
|
out.Note = "Device is known to Emby but has no live session. Turn on the Home Assistant media_player and launch the Emby client."
|
|
}
|
|
return out, true
|
|
}
|
|
}
|
|
out.Note = "ERROR: no player matched that name, device id, or IP"
|
|
return out, false
|
|
}
|
|
|
|
func matchLink(ql string, links []config.PlayerLink) (*config.PlayerLink, string) {
|
|
for i := range links {
|
|
l := &links[i]
|
|
for _, n := range l.Names {
|
|
if strings.ToLower(strings.TrimSpace(n)) == ql {
|
|
return l, n
|
|
}
|
|
}
|
|
if strings.ToLower(l.HAEntity) == ql {
|
|
return l, l.HAEntity
|
|
}
|
|
if l.EmbyDeviceID != "" && strings.ToLower(l.EmbyDeviceID) == ql {
|
|
return l, l.EmbyDeviceID
|
|
}
|
|
if l.EmbyDeviceName != "" && strings.ToLower(l.EmbyDeviceName) == ql {
|
|
return l, l.EmbyDeviceName
|
|
}
|
|
if ip := emby.HostOf(l.DeviceIP); ip != "" && strings.ToLower(ip) == ql {
|
|
return l, ip
|
|
}
|
|
}
|
|
return nil, ""
|
|
}
|
|
|
|
func matchSession(link *config.PlayerLink, players []emby.PlayerSession) (emby.PlayerSession, bool) {
|
|
linkIP := emby.HostOf(link.DeviceIP)
|
|
for _, p := range players {
|
|
if link.EmbyDeviceID != "" && p.DeviceID == link.EmbyDeviceID && p.Online {
|
|
return p, true
|
|
}
|
|
}
|
|
for _, p := range players {
|
|
if linkIP != "" && emby.HostOf(p.DeviceIPAddress) == linkIP && p.Online {
|
|
return p, true
|
|
}
|
|
if link.EmbyDeviceName != "" && strings.EqualFold(p.DeviceName, link.EmbyDeviceName) && p.Online {
|
|
return p, true
|
|
}
|
|
}
|
|
return emby.PlayerSession{}, false
|
|
}
|
|
|
|
func sessionMatchesQuery(ql string, p emby.PlayerSession) bool {
|
|
if strings.ToLower(p.DeviceID) == ql {
|
|
return true
|
|
}
|
|
if strings.ToLower(p.DeviceName) == ql {
|
|
return true
|
|
}
|
|
if strings.ToLower(p.ClientName) == ql {
|
|
return true
|
|
}
|
|
if ip := emby.HostOf(p.DeviceIPAddress); ip != "" && strings.ToLower(ip) == ql {
|
|
return true
|
|
}
|
|
if strings.ToLower(p.UserName) == ql {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func fillFromSession(out *ResolvedPlayer, p emby.PlayerSession) {
|
|
out.SessionID = p.SessionID
|
|
out.Online = p.Online
|
|
out.UserName = p.UserName
|
|
out.UserID = p.UserID
|
|
if out.EmbyDeviceID == "" {
|
|
out.EmbyDeviceID = p.DeviceID
|
|
}
|
|
if out.EmbyDeviceName == "" {
|
|
out.EmbyDeviceName = p.DeviceName
|
|
}
|
|
if out.DeviceIP == "" {
|
|
out.DeviceIP = emby.HostOf(p.DeviceIPAddress)
|
|
}
|
|
}
|
|
|
|
func firstNonEmpty(vals ...string) string {
|
|
for _, v := range vals {
|
|
if strings.TrimSpace(v) != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func mergeUserFilters(toolUsers string, configured []string) []string {
|
|
fromTool := config.SplitCSV(toolUsers)
|
|
if len(fromTool) > 0 {
|
|
return fromTool
|
|
}
|
|
return configured
|
|
}
|