From f0055b76fece3b3bafdb267cfa32c2661c7ff84a Mon Sep 17 00:00:00 2001 From: truewhile <62226914+truewhile@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:45:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Emby=20=E8=BF=9C=E7=A8=8B=E6=8C=82?= =?UTF-8?q?=E8=BD=BD=E8=B4=A6=E5=8F=B7=E7=BC=96=E8=BE=91=E4=BF=9D=E7=95=99?= =?UTF-8?q?=E5=87=AD=E6=8D=AE=20+=20=E4=BB=A3=E7=90=86=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E5=9B=9E=E6=98=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UpdateStrmAccount 对 emby_remote 做合并式更新:只覆盖传入键,未提及的 地址/用户名/密码/token 密文保留,避免编辑代理开关时清空凭据 - 账号列表/编辑表单回显 proxy_play(服务端解密后下发) - 前端 StrmAccount 类型与编辑对话框支持 proxy_play 回显 --- internal/handler/strm.go | 22 ++++++++++++------ internal/service/emby_remote.go | 9 ++++++++ internal/service/strm_service.go | 38 +++++++++++++++++++++++++++++++- web/src/pages/StrmDialogs.tsx | 2 +- web/src/types/strm.ts | 2 ++ 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/internal/handler/strm.go b/internal/handler/strm.go index 8f897fc..ead666f 100644 --- a/internal/handler/strm.go +++ b/internal/handler/strm.go @@ -28,17 +28,25 @@ type strmAccountView struct { model.StrmAccount HasCredential bool `json:"has_credential"` ProviderLabel string `json:"provider_label"` + // ProxyPlay 仅远程 Emby 挂载账号返回:播放流量是否经过 MMTL 代理(编辑回显用)。 + ProxyPlay *bool `json:"proxy_play,omitempty"` } -func strmAccountViews(accounts []model.StrmAccount) []strmAccountView { +func strmAccountViews(svc *service.Container, accounts []model.StrmAccount) []strmAccountView { out := make([]strmAccountView, 0, len(accounts)) for i := range accounts { a := accounts[i] - out = append(out, strmAccountView{ + view := strmAccountView{ StrmAccount: a, HasCredential: service.HasStrmAccountCredential(&a), ProviderLabel: providerLabelOf(a.Provider), - }) + } + if a.Provider == model.StrmProviderEmbyRemote && svc != nil && svc.EmbyRemote != nil { + if proxyPlay, err := svc.EmbyRemote.ProxyPlayOf(&a); err == nil { + view.ProxyPlay = &proxyPlay + } + } + out = append(out, view) } return out } @@ -58,7 +66,7 @@ func listStrmAccountsHandler(svc *service.Container) gin.HandlerFunc { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, strmAccountViews(accounts)) + c.JSON(http.StatusOK, strmAccountViews(svc, accounts)) } } @@ -74,7 +82,7 @@ func createStrmAccountHandler(svc *service.Container) gin.HandlerFunc { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - views := strmAccountViews([]model.StrmAccount{*acct}) + views := strmAccountViews(svc, []model.StrmAccount{*acct}) c.JSON(http.StatusOK, views[0]) } } @@ -92,7 +100,7 @@ func updateStrmAccountHandler(svc *service.Container) gin.HandlerFunc { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - views := strmAccountViews([]model.StrmAccount{*acct}) + views := strmAccountViews(svc, []model.StrmAccount{*acct}) c.JSON(http.StatusOK, views[0]) } } @@ -114,7 +122,7 @@ func testStrmAccountHandler(svc *service.Container) gin.HandlerFunc { c.JSON(http.StatusNotFound, gin.H{"error": "网盘账号不存在"}) return } - views := strmAccountViews([]model.StrmAccount{*acct}) + views := strmAccountViews(svc, []model.StrmAccount{*acct}) c.JSON(http.StatusOK, views[0]) } } diff --git a/internal/service/emby_remote.go b/internal/service/emby_remote.go index 45c66c7..924cb4c 100644 --- a/internal/service/emby_remote.go +++ b/internal/service/emby_remote.go @@ -272,6 +272,15 @@ func (r *EmbyRemoteService) TestConnection(ctx context.Context, acct *model.Strm return r.doGet(ctx, acct, cfg, "/System/Info", nil, &out) } +// ProxyPlayOf 返回账号是否配置了播放代理(供账号列表/编辑回显)。 +func (r *EmbyRemoteService) ProxyPlayOf(acct *model.StrmAccount) (bool, error) { + cfg, err := r.configOf(acct) + if err != nil { + return false, err + } + return cfg.ProxyPlay, nil +} + // ─── 元数据 / 目录聚合 ───────────────────────────────────────────────────────── // RemoteViews 拉取远程媒体库(View)列表,返回远程原始 view map(未重写)。 diff --git a/internal/service/strm_service.go b/internal/service/strm_service.go index b362f9a..994669e 100644 --- a/internal/service/strm_service.go +++ b/internal/service/strm_service.go @@ -237,6 +237,33 @@ func (s *StrmService) strmAccountConfig(acct *model.StrmAccount) (map[string]str return cfg, nil } +// mergeEmbyRemoteConfig 对远程 Emby 账号配置做合并式更新:config 中出现的键 +// 覆盖写入(敏感键按明文加密),未出现的键保留原密文;显式空字符串=清除。 +func (s *StrmService) mergeEmbyRemoteConfig(existing string, config map[string]string) (string, error) { + out := map[string]string{} + if strings.TrimSpace(existing) != "" { + if err := json.Unmarshal([]byte(existing), &out); err != nil { + return "", fmt.Errorf("decode account config: %w", err) + } + } + for k, v := range config { + if v == "" { + delete(out, k) + continue + } + if strmContains(StrmAccountSecretKeys, k) { + out[k] = s.crypto.Encrypt(v) + } else { + out[k] = v + } + } + data, err := json.Marshal(out) + if err != nil { + return "", err + } + return string(data), nil +} + // HasStrmAccountCredential 报告账号是否已配置核心凭据(用于前端展示)。 func HasStrmAccountCredential(acct *model.StrmAccount) bool { switch acct.Provider { @@ -292,7 +319,16 @@ func (s *StrmService) UpdateStrmAccount(ctx context.Context, id, name string, en acct.Enabled = *enabled } if len(config) > 0 { - enc, err := s.strmAccountConfigJSON(config, true) + var enc string + var err error + if acct.Provider == model.StrmProviderEmbyRemote { + // 远程 Emby 账号:合并式更新。config 中出现的键覆盖(敏感键按明文 + // 加密写入),未出现的键保留原密文——避免编辑「代理开关」时把已 + // 保存的地址与凭据清空。 + enc, err = s.mergeEmbyRemoteConfig(acct.Config, config) + } else { + enc, err = s.strmAccountConfigJSON(config, true) + } if err != nil { return nil, err } diff --git a/web/src/pages/StrmDialogs.tsx b/web/src/pages/StrmDialogs.tsx index 41d5121..a2f8bd8 100644 --- a/web/src/pages/StrmDialogs.tsx +++ b/web/src/pages/StrmDialogs.tsx @@ -111,7 +111,7 @@ export function StrmAccountDialog({ const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [token, setToken] = useState('') - const [proxyPlay, setProxyPlay] = useState(false) + const [proxyPlay, setProxyPlay] = useState(existing?.proxy_play ?? false) const [saving, setSaving] = useState(false) const buildConfig = (): Record => { diff --git a/web/src/types/strm.ts b/web/src/types/strm.ts index 42df572..709ce9d 100644 --- a/web/src/types/strm.ts +++ b/web/src/types/strm.ts @@ -21,6 +21,8 @@ export interface StrmAccount { last_test_ok: boolean has_credential: boolean provider_label: string + /** 仅远程 Emby 挂载账号返回:播放流量是否经过 MMTL 代理 */ + proxy_play?: boolean } export interface StrmAccountInput {