Files
gronod 7bcf5322e1 feat: emby-mcp 1.0.16 player owner filter and HA links
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.
2026-09-25 19:58:58 +00:00

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
}