mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-03 05:44:44 +08:00
fix(emby): use series identity in TV webhooks
This commit is contained in:
@@ -756,6 +756,26 @@ class Emby:
|
||||
logger.error(f"连接/Users/{self.user}/Items/{itemid}出错:" + str(e))
|
||||
return None
|
||||
|
||||
def _get_series_identity(
|
||||
self,
|
||||
series_id: Optional[str],
|
||||
) -> Tuple[Optional[MediaSource], Optional[str]]:
|
||||
"""查询 Emby Series 条目的规范身份,查询失败时返回空身份。"""
|
||||
if not series_id or not self._host or not self._apikey or not self.user:
|
||||
return None, None
|
||||
url = f"{self._host}emby/Users/{self.user}/Items/{series_id}"
|
||||
params = {
|
||||
"api_key": self._apikey,
|
||||
"Fields": "ProviderIds",
|
||||
}
|
||||
try:
|
||||
res = RequestUtils().get_res(url, params)
|
||||
if res and res.status_code == 200:
|
||||
return MediaServerIdentityHelper.from_provider_ids(res.json().get("ProviderIds"))
|
||||
except Exception as e:
|
||||
logger.error(f"获取 Emby 剧集身份出错:{str(e)}")
|
||||
return None, None
|
||||
|
||||
def get_items_count(self, parent: Union[str, int], include_item_types: str = "Movie,Series") -> Optional[int]:
|
||||
"""
|
||||
获取指定媒体库可同步的电影和剧集总数
|
||||
@@ -1063,6 +1083,8 @@ class Emby:
|
||||
"PlaylistLength": 40
|
||||
}
|
||||
}
|
||||
|
||||
Episode 和 Season 的 ProviderIds 可能标识单集或季;统一媒体身份应从 SeriesId 对应的剧集条目读取。
|
||||
"""
|
||||
if not form and not args:
|
||||
return None
|
||||
@@ -1117,9 +1139,12 @@ class Emby:
|
||||
eventItem.item_id = message.get('Item', {}).get('Id')
|
||||
|
||||
eventItem.item_path = message.get('Item', {}).get('Path')
|
||||
eventItem.media_source, eventItem.media_id = MediaServerIdentityHelper.from_provider_ids(
|
||||
message.get('Item', {}).get('ProviderIds')
|
||||
)
|
||||
if message.get('Item', {}).get('Type') in {"Episode", "Season"}:
|
||||
eventItem.media_source, eventItem.media_id = self._get_series_identity(eventItem.item_id)
|
||||
else:
|
||||
eventItem.media_source, eventItem.media_id = MediaServerIdentityHelper.from_provider_ids(
|
||||
message.get('Item', {}).get('ProviderIds')
|
||||
)
|
||||
if message.get('Item', {}).get('Overview') and len(message.get('Item', {}).get('Overview')) > 100:
|
||||
eventItem.overview = str(message.get('Item', {}).get('Overview'))[:100] + "..."
|
||||
else:
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import json
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.emby.emby import Emby
|
||||
from app.modules.jellyfin.jellyfin import Jellyfin
|
||||
from app.modules.zspace.zspace import ZSpace
|
||||
from app.schemas import WebhookEventInfo
|
||||
from app.schemas.mediaserver import WebhookEventInfo
|
||||
from app.schemas.types import MediaSource
|
||||
|
||||
|
||||
@@ -79,3 +80,78 @@ def test_emby_family_webhook_uses_provider_identity_pair(client_class: type) ->
|
||||
assert event is not None
|
||||
assert event.media_source == MediaSource.Douban
|
||||
assert event.media_id == "2002"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("item_type", ["Episode", "Season"])
|
||||
def test_emby_tv_webhook_uses_series_provider_identity(monkeypatch, item_type: str) -> None:
|
||||
"""Episode 和 Season webhook 应使用 Series 条目的媒体主身份。"""
|
||||
client = Emby.__new__(Emby)
|
||||
get_series_identity = Mock(return_value=(MediaSource.TMDB, "286686"))
|
||||
monkeypatch.setattr(client, "_get_series_identity", get_series_identity, raising=False)
|
||||
monkeypatch.setattr(client, "get_remote_image_by_id", lambda **kwargs: None)
|
||||
|
||||
event = client.get_webhook_message({
|
||||
"data": json.dumps({
|
||||
"Event": "playback.start",
|
||||
"Item": {
|
||||
"Type": item_type,
|
||||
"Id": "child-item-1",
|
||||
"Name": "第 24 集",
|
||||
"SeriesName": "生逢其时",
|
||||
"SeriesId": "series-item-1",
|
||||
"ProviderIds": {"Tmdb": "7750138"},
|
||||
},
|
||||
})
|
||||
}, {})
|
||||
|
||||
assert event is not None
|
||||
get_series_identity.assert_called_once_with("series-item-1")
|
||||
assert event.item_id == "series-item-1"
|
||||
assert event.media_source == MediaSource.TMDB
|
||||
assert event.media_id == "286686"
|
||||
|
||||
|
||||
def test_emby_tv_webhook_keeps_identity_empty_when_series_lookup_fails(monkeypatch) -> None:
|
||||
"""Series 身份查询失败时不得回退为 Episode 或 Season 的 ProviderIds。"""
|
||||
client = Emby.__new__(Emby)
|
||||
monkeypatch.setattr(client, "_get_series_identity", Mock(return_value=(None, None)), raising=False)
|
||||
monkeypatch.setattr(client, "get_remote_image_by_id", lambda **kwargs: None)
|
||||
|
||||
event = client.get_webhook_message({
|
||||
"data": json.dumps({
|
||||
"Event": "playback.start",
|
||||
"Item": {
|
||||
"Type": "Episode",
|
||||
"Id": "episode-item-1",
|
||||
"Name": "第 24 集",
|
||||
"SeriesId": "series-item-1",
|
||||
"ProviderIds": {"Tmdb": "7750138"},
|
||||
},
|
||||
})
|
||||
}, {})
|
||||
|
||||
assert event is not None
|
||||
assert event.item_id == "series-item-1"
|
||||
assert event.media_source is None
|
||||
assert event.media_id is None
|
||||
|
||||
|
||||
def test_emby_series_identity_requests_provider_ids(monkeypatch) -> None:
|
||||
"""系列身份查询应只请求 Series 的 ProviderIds 并选择规范来源。"""
|
||||
client = Emby.__new__(Emby)
|
||||
client._host = "http://emby.local/"
|
||||
client._apikey = "test-key"
|
||||
client.user = "user-1"
|
||||
request = Mock()
|
||||
response = Mock(status_code=200)
|
||||
response.json.return_value = {"ProviderIds": {"Tmdb": "286686", "Tvdb": "468626"}}
|
||||
request.get_res.return_value = response
|
||||
monkeypatch.setattr("app.modules.emby.emby.RequestUtils", lambda: request)
|
||||
|
||||
identity = client._get_series_identity("series-item-1")
|
||||
|
||||
assert identity == (MediaSource.TMDB, "286686")
|
||||
request.get_res.assert_called_once_with(
|
||||
"http://emby.local/emby/Users/user-1/Items/series-item-1",
|
||||
{"api_key": "test-key", "Fields": "ProviderIds"},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user