From fd9eef208980a3492e65242e61c73d0ec016a6c9 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sat, 23 Sep 2023 09:20:51 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=94=AF=E6=8C=81=E5=A4=9A=E5=AA=92?= =?UTF-8?q?=E4=BD=93=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=90=8C=E6=97=B6=E4=BD=BF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- app/api/endpoints/dashboard.py | 20 +++++---- app/chain/__init__.py | 12 +++--- app/chain/dashboard.py | 4 +- app/chain/mediaserver.py | 74 +++++++++++++++++--------------- app/core/config.py | 4 +- app/modules/emby/__init__.py | 32 +++++++------- app/modules/emby/emby.py | 19 ++++++-- app/modules/jellyfin/__init__.py | 27 +++++++----- app/modules/jellyfin/jellyfin.py | 16 ++++++- app/modules/plex/__init__.py | 27 +++++++----- app/modules/plex/plex.py | 18 ++++++-- 12 files changed, 159 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index b838d1d5..7b075dce 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ docker pull jxxghp/moviepilot:latest - **DOWNLOADER_MONITOR:** 下载器监控,`true`/`false`,默认为`true`,开启后下载完成时才会自动整理入库 -- **MEDIASERVER:** 媒体服务器,支持`emby`/`jellyfin`/`plex`,同时还需要配置对应媒体服务器的环境变量,非对应媒体服务器的变量可删除,推荐使用`emby` +- **MEDIASERVER:** 媒体服务器,支持`emby`/`jellyfin`/`plex`,同时开启多个使用`,`分隔。还需要配置对应媒体服务器的环境变量,非对应媒体服务器的变量可删除,推荐使用`emby` - `emby`设置项: diff --git a/app/api/endpoints/dashboard.py b/app/api/endpoints/dashboard.py index 1e9d451b..44832127 100644 --- a/app/api/endpoints/dashboard.py +++ b/app/api/endpoints/dashboard.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Any, List +from typing import Any, List, Optional from fastapi import APIRouter, Depends from requests import Session @@ -24,14 +24,16 @@ def statistic(db: Session = Depends(get_db), """ 查询媒体数量统计信息 """ - media_statistic = DashboardChain(db).media_statistic() - if media_statistic: - return schemas.Statistic( - movie_count=media_statistic.movie_count, - tv_count=media_statistic.tv_count, - episode_count=media_statistic.episode_count, - user_count=media_statistic.user_count - ) + media_statistics: Optional[List[schemas.Statistic]] = DashboardChain(db).media_statistic() + if media_statistics: + # 汇总各媒体库统计信息 + ret_statistic = schemas.Statistic() + for media_statistic in media_statistics: + ret_statistic.movie_count += media_statistic.movie_count + ret_statistic.tv_count += media_statistic.tv_count + ret_statistic.episode_count += media_statistic.episode_count + ret_statistic.user_count += media_statistic.user_count + return ret_statistic else: return schemas.Statistic() diff --git a/app/chain/__init__.py b/app/chain/__init__.py index 8f0675dc..25af7083 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -336,14 +336,14 @@ class ChainBase(metaclass=ABCMeta): """ return self.run_module("media_exists", mediainfo=mediainfo, itemid=itemid) - def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]: + def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None: """ 刷新媒体库 :param mediainfo: 识别的媒体信息 :param file_path: 文件路径 :return: 成功或失败 """ - return self.run_module("refresh_mediaserver", mediainfo=mediainfo, file_path=file_path) + self.run_module("refresh_mediaserver", mediainfo=mediainfo, file_path=file_path) def post_message(self, message: Notification) -> None: """ @@ -391,22 +391,22 @@ class ChainBase(metaclass=ABCMeta): :param mediainfo: 识别的媒体信息 :return: 成功或失败 """ - return self.run_module("scrape_metadata", path=path, mediainfo=mediainfo) + self.run_module("scrape_metadata", path=path, mediainfo=mediainfo) def register_commands(self, commands: Dict[str, dict]) -> None: """ 注册菜单命令 """ - return self.run_module("register_commands", commands=commands) + self.run_module("register_commands", commands=commands) def scheduler_job(self) -> None: """ 定时任务,每10分钟调用一次,模块实现该接口以实现定时服务 """ - return self.run_module("scheduler_job") + self.run_module("scheduler_job") def clear_cache(self) -> None: """ 清理缓存,模块实现该接口响应清理缓存事件 """ - return self.run_module("clear_cache") + self.run_module("clear_cache") diff --git a/app/chain/dashboard.py b/app/chain/dashboard.py index c2b5c65f..e32c30f7 100644 --- a/app/chain/dashboard.py +++ b/app/chain/dashboard.py @@ -1,3 +1,5 @@ +from typing import Optional, List + from app import schemas from app.chain import ChainBase @@ -6,7 +8,7 @@ class DashboardChain(ChainBase): """ 各类仪表板统计处理链 """ - def media_statistic(self) -> schemas.Statistic: + def media_statistic(self) -> Optional[List[schemas.Statistic]]: """ 媒体数量统计 """ diff --git a/app/chain/mediaserver.py b/app/chain/mediaserver.py index 7b04e2bf..094d2e3b 100644 --- a/app/chain/mediaserver.py +++ b/app/chain/mediaserver.py @@ -23,23 +23,23 @@ class MediaServerChain(ChainBase): def __init__(self, db: Session = None): super().__init__(db) - def librarys(self) -> List[schemas.MediaServerLibrary]: + def librarys(self, server: str) -> List[schemas.MediaServerLibrary]: """ 获取媒体服务器所有媒体库 """ - return self.run_module("mediaserver_librarys") + return self.run_module("mediaserver_librarys", server=server) - def items(self, library_id: Union[str, int]) -> Generator: + def items(self, server: str, library_id: Union[str, int]) -> Generator: """ 获取媒体服务器所有项目 """ - return self.run_module("mediaserver_items", library_id=library_id) + return self.run_module("mediaserver_items", server=server, library_id=library_id) - def episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]: + def episodes(self, server: str, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]: """ 获取媒体服务器剧集信息 """ - return self.run_module("mediaserver_tv_episodes", item_id=item_id) + return self.run_module("mediaserver_tv_episodes", server=server, item_id=item_id) def remote_sync(self, channel: MessageChannel, userid: Union[int, str]): """ @@ -59,7 +59,6 @@ class MediaServerChain(ChainBase): # 媒体服务器同步使用独立的会话 _db = SessionFactory() _dbOper = MediaServerOper(_db) - logger.info("开始同步媒体库数据 ...") # 汇总统计 total_count = 0 # 清空登记薄 @@ -67,35 +66,42 @@ class MediaServerChain(ChainBase): # 同步黑名单 sync_blacklist = settings.MEDIASERVER_SYNC_BLACKLIST.split( ",") if settings.MEDIASERVER_SYNC_BLACKLIST else [] - for library in self.librarys(): - if library.name in sync_blacklist: + # 设置的媒体服务器 + if not settings.MEDIASERVER: + return + mediaservers = settings.MEDIASERVER.split(",") + # 遍历媒体服务器 + for mediaserver in mediaservers: + logger.info(f"开始同步媒体库 {mediaserver} 的数据 ...") + for library in self.librarys(mediaserver): # 同步黑名单 跳过 - continue - logger.info(f"正在同步媒体库 {library.name} ...") - library_count = 0 - for item in self.items(library.id): - if not item: + if library.name in sync_blacklist: continue - if not item.item_id: - continue - # 计数 - library_count += 1 - seasoninfo = {} - # 类型 - item_type = "电视剧" if item.item_type in ['Series', 'show'] else "电影" - if item_type == "电视剧": - # 查询剧集信息 - espisodes_info = self.episodes(item.item_id) or [] - for episode in espisodes_info: - seasoninfo[episode.season] = episode.episodes - # 插入数据 - item_dict = item.dict() - item_dict['seasoninfo'] = json.dumps(seasoninfo) - item_dict['item_type'] = item_type - _dbOper.add(**item_dict) - logger.info(f"媒体库 {library.name} 同步完成,共同步数量:{library_count}") - # 总数累加 - total_count += library_count + logger.info(f"正在同步 {mediaserver} 媒体库 {library.name} ...") + library_count = 0 + for item in self.items(mediaserver, library.id): + if not item: + continue + if not item.item_id: + continue + # 计数 + library_count += 1 + seasoninfo = {} + # 类型 + item_type = "电视剧" if item.item_type in ['Series', 'show'] else "电影" + if item_type == "电视剧": + # 查询剧集信息 + espisodes_info = self.episodes(mediaserver, item.item_id) or [] + for episode in espisodes_info: + seasoninfo[episode.season] = episode.episodes + # 插入数据 + item_dict = item.dict() + item_dict['seasoninfo'] = json.dumps(seasoninfo) + item_dict['item_type'] = item_type + _dbOper.add(**item_dict) + logger.info(f"{mediaserver} 媒体库 {library.name} 同步完成,共同步数量:{library_count}") + # 总数累加 + total_count += library_count # 关闭数据库连接 if _db: _db.close() diff --git a/app/core/config.py b/app/core/config.py index bd5c3407..90243258 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -76,7 +76,7 @@ class Settings(BaseSettings): AUTH_SITE: str = "" # 交互搜索自动下载用户ID,使用,分割 AUTO_DOWNLOAD_USER: str = None - # 消息通知渠道 telegram/wechat/slack + # 消息通知渠道 telegram/wechat/slack,多个通知渠道用,分隔 MESSAGER: str = "telegram" # WeChat企业ID WECHAT_CORPID: str = None @@ -142,7 +142,7 @@ class Settings(BaseSettings): DOWNLOAD_CATEGORY: bool = False # 下载站点字幕 DOWNLOAD_SUBTITLE: bool = True - # 媒体服务器 emby/jellyfin/plex + # 媒体服务器 emby/jellyfin/plex,多个媒体服务器,分割 MEDIASERVER: str = "emby" # 入库刷新媒体库 REFRESH_MEDIASERVER: bool = True diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index 38c516aa..7ae34ab0 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -1,4 +1,3 @@ -import json from pathlib import Path from typing import Optional, Tuple, Union, Any, List, Generator @@ -41,7 +40,7 @@ class EmbyModule(_ModuleBase): # Emby认证 return self.emby.authenticate(name, password) - def webhook_parser(self, body: Any, form: Any, args: Any) -> WebhookEventInfo: + def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[WebhookEventInfo]: """ 解析Webhook报文体 :param body: 请求体 @@ -49,11 +48,7 @@ class EmbyModule(_ModuleBase): :param args: 请求参数 :return: 字典,解析为消息时需要包含:title、text、image """ - if form and form.get("data"): - result = form.get("data") - else: - result = json.dumps(dict(args)) - return self.emby.get_webhook_message(result) + return self.emby.get_webhook_message(form, args) def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]: """ @@ -87,7 +82,7 @@ class EmbyModule(_ModuleBase): logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}") return ExistMediaInfo(type=MediaType.TV, seasons=tvs) - def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]: + def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None: """ 刷新媒体库 :param mediainfo: 识别的媒体信息 @@ -103,25 +98,27 @@ class EmbyModule(_ModuleBase): target_path=file_path ) ] - return self.emby.refresh_library_by_items(items) + self.emby.refresh_library_by_items(items) - def media_statistic(self) -> schemas.Statistic: + def media_statistic(self) -> List[schemas.Statistic]: """ 媒体数量统计 """ media_statistic = self.emby.get_medias_count() user_count = self.emby.get_user_count() - return schemas.Statistic( + return [schemas.Statistic( movie_count=media_statistic.get("MovieCount") or 0, tv_count=media_statistic.get("SeriesCount") or 0, episode_count=media_statistic.get("EpisodeCount") or 0, user_count=user_count or 0 - ) + )] - def mediaserver_librarys(self) -> List[schemas.MediaServerLibrary]: + def mediaserver_librarys(self, server: str) -> Optional[List[schemas.MediaServerLibrary]]: """ 媒体库列表 """ + if server != "emby": + return None librarys = self.emby.get_librarys() if not librarys: return [] @@ -133,10 +130,12 @@ class EmbyModule(_ModuleBase): path=library.get("path") ) for library in librarys] - def mediaserver_items(self, library_id: str) -> Generator: + def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]: """ 媒体库项目列表 """ + if server != "emby": + return None items = self.emby.get_items(library_id) for item in items: yield schemas.MediaServerItem( @@ -153,10 +152,13 @@ class EmbyModule(_ModuleBase): path=item.get("path"), ) - def mediaserver_tv_episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]: + def mediaserver_tv_episodes(self, server: str, + item_id: Union[str, int]) -> Optional[List[schemas.MediaServerSeasonInfo]]: """ 获取剧集信息 """ + if server != "emby": + return None seasoninfo = self.emby.get_tv_episodes(item_id=item_id) if not seasoninfo: return [] diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index 583836fa..5873097d 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -545,7 +545,7 @@ class Emby(metaclass=Singleton): logger.error(f"连接Users/Items出错:" + str(e)) yield {} - def get_webhook_message(self, message_str: str) -> WebhookEventInfo: + def get_webhook_message(self, form: any, args: dict) -> Optional[WebhookEventInfo]: """ 解析Emby Webhook报文 电影: @@ -783,9 +783,22 @@ class Emby(metaclass=Singleton): } } """ - message = json.loads(message_str) + if not form and not args: + return None + try: + if form and form.get("data"): + result = form.get("data") + else: + result = json.dumps(dict(args)) + message = json.loads(result) + except Exception as e: + logger.debug(f"解析emby webhook报文出错:" + str(e)) + return None + eventType = message.get('Event') + if not eventType: + return None logger.info(f"接收到emby webhook:{message}") - eventItem = WebhookEventInfo(event=message.get('Event', ''), channel="emby") + eventItem = WebhookEventInfo(event=eventType, channel="emby") if message.get('Item'): if message.get('Item', {}).get('Type') == 'Episode': eventItem.item_type = "TV" diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index b57c4c0e..d11c04c7 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -41,7 +41,7 @@ class JellyfinModule(_ModuleBase): # Jellyfin认证 return self.jellyfin.authenticate(name, password) - def webhook_parser(self, body: Any, form: Any, args: Any) -> WebhookEventInfo: + def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[WebhookEventInfo]: """ 解析Webhook报文体 :param body: 请求体 @@ -49,7 +49,7 @@ class JellyfinModule(_ModuleBase): :param args: 请求参数 :return: 字典,解析为消息时需要包含:title、text、image """ - return self.jellyfin.get_webhook_message(json.loads(body)) + return self.jellyfin.get_webhook_message(body) def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]: """ @@ -83,32 +83,34 @@ class JellyfinModule(_ModuleBase): logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}") return ExistMediaInfo(type=MediaType.TV, seasons=tvs) - def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]: + def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None: """ 刷新媒体库 :param mediainfo: 识别的媒体信息 :param file_path: 文件路径 :return: 成功或失败 """ - return self.jellyfin.refresh_root_library() + self.jellyfin.refresh_root_library() - def media_statistic(self) -> schemas.Statistic: + def media_statistic(self) -> List[schemas.Statistic]: """ 媒体数量统计 """ media_statistic = self.jellyfin.get_medias_count() user_count = self.jellyfin.get_user_count() - return schemas.Statistic( + return [schemas.Statistic( movie_count=media_statistic.get("MovieCount") or 0, tv_count=media_statistic.get("SeriesCount") or 0, episode_count=media_statistic.get("EpisodeCount") or 0, user_count=user_count or 0 - ) + )] - def mediaserver_librarys(self) -> List[schemas.MediaServerLibrary]: + def mediaserver_librarys(self, server: str) -> Optional[List[schemas.MediaServerLibrary]]: """ 媒体库列表 """ + if server != "jellyfin": + return None librarys = self.jellyfin.get_librarys() if not librarys: return [] @@ -120,10 +122,12 @@ class JellyfinModule(_ModuleBase): path=library.get("path") ) for library in librarys] - def mediaserver_items(self, library_id: str) -> Generator: + def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]: """ 媒体库项目列表 """ + if server != "jellyfin": + return None items = self.jellyfin.get_items(library_id) for item in items: yield schemas.MediaServerItem( @@ -140,10 +144,13 @@ class JellyfinModule(_ModuleBase): path=item.get("path"), ) - def mediaserver_tv_episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]: + def mediaserver_tv_episodes(self, server: str, + item_id: Union[str, int]) -> Optional[List[schemas.MediaServerSeasonInfo]]: """ 获取剧集信息 """ + if server != "jellyfin": + return None seasoninfo = self.jellyfin.get_tv_episodes(item_id=item_id) if not seasoninfo: return [] diff --git a/app/modules/jellyfin/jellyfin.py b/app/modules/jellyfin/jellyfin.py index 28ee7775..2c684fa6 100644 --- a/app/modules/jellyfin/jellyfin.py +++ b/app/modules/jellyfin/jellyfin.py @@ -387,7 +387,7 @@ class Jellyfin(metaclass=Singleton): logger.error(f"连接Library/Refresh出错:" + str(e)) return False - def get_webhook_message(self, message: dict) -> WebhookEventInfo: + def get_webhook_message(self, body: any) -> Optional[WebhookEventInfo]: """ 解析Jellyfin报文 { @@ -450,9 +450,21 @@ class Jellyfin(metaclass=Singleton): "UserId": "9783d2432b0d40a8a716b6aa46xxxxx" } """ + if not body: + return None + try: + message = json.loads(body) + except Exception as e: + logger.debug(f"解析Jellyfin Webhook报文出错:" + str(e)) + return None + if not message: + return None logger.info(f"接收到jellyfin webhook:{message}") + eventType = message.get('NotificationType') + if not eventType: + return None eventItem = WebhookEventInfo( - event=message.get('NotificationType', ''), + event=eventType, channel="jellyfin" ) eventItem.item_id = message.get('ItemId') diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index 35c7e16d..0cb1aa45 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -31,7 +31,7 @@ class PlexModule(_ModuleBase): if not self.plex.is_inactive(): self.plex = Plex() - def webhook_parser(self, body: Any, form: Any, args: Any) -> WebhookEventInfo: + def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[WebhookEventInfo]: """ 解析Webhook报文体 :param body: 请求体 @@ -39,7 +39,7 @@ class PlexModule(_ModuleBase): :param args: 请求参数 :return: 字典,解析为消息时需要包含:title、text、image """ - return self.plex.get_webhook_message(form.get("payload")) + return self.plex.get_webhook_message(form) def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]: """ @@ -77,7 +77,7 @@ class PlexModule(_ModuleBase): logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}") return ExistMediaInfo(type=MediaType.TV, seasons=tvs) - def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]: + def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None: """ 刷新媒体库 :param mediainfo: 识别的媒体信息 @@ -93,24 +93,26 @@ class PlexModule(_ModuleBase): target_path=file_path ) ] - return self.plex.refresh_library_by_items(items) + self.plex.refresh_library_by_items(items) - def media_statistic(self) -> schemas.Statistic: + def media_statistic(self) -> List[schemas.Statistic]: """ 媒体数量统计 """ media_statistic = self.plex.get_medias_count() - return schemas.Statistic( + return [schemas.Statistic( movie_count=media_statistic.get("MovieCount") or 0, tv_count=media_statistic.get("SeriesCount") or 0, episode_count=media_statistic.get("EpisodeCount") or 0, user_count=1 - ) + )] - def mediaserver_librarys(self) -> List[schemas.MediaServerLibrary]: + def mediaserver_librarys(self, server: str) -> Optional[List[schemas.MediaServerLibrary]]: """ 媒体库列表 """ + if server != "plex": + return None librarys = self.plex.get_librarys() if not librarys: return [] @@ -122,10 +124,12 @@ class PlexModule(_ModuleBase): path=library.get("path") ) for library in librarys] - def mediaserver_items(self, library_id: str) -> Generator: + def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]: """ 媒体库项目列表 """ + if server != "plex": + return None items = self.plex.get_items(library_id) for item in items: yield schemas.MediaServerItem( @@ -142,10 +146,13 @@ class PlexModule(_ModuleBase): path=item.get("path"), ) - def mediaserver_tv_episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]: + def mediaserver_tv_episodes(self, server: str, + item_id: Union[str, int]) -> Optional[List[schemas.MediaServerSeasonInfo]]: """ 获取剧集信息 """ + if server != "plex": + return None seasoninfo = self.plex.get_tv_episodes(item_id=item_id) if not seasoninfo: return [] diff --git a/app/modules/plex/plex.py b/app/modules/plex/plex.py index 17fad119..c9815102 100644 --- a/app/modules/plex/plex.py +++ b/app/modules/plex/plex.py @@ -353,7 +353,7 @@ class Plex(metaclass=Singleton): logger.error(f"获取媒体库列表出错:{err}") yield {} - def get_webhook_message(self, message_str: str) -> WebhookEventInfo: + def get_webhook_message(self, form: any) -> Optional[WebhookEventInfo]: """ 解析Plex报文 eventItem 字段的含义 @@ -457,9 +457,21 @@ class Plex(metaclass=Singleton): } } """ - message = json.loads(message_str) + if not form: + return None + payload = form.get("payload") + if not payload: + return None + try: + message = json.loads(payload) + except Exception as e: + logger.debug(f"解析plex webhook出错:{str(e)}") + return None + eventType = message.get('event') + if not eventType: + return None logger.info(f"接收到plex webhook:{message}") - eventItem = WebhookEventInfo(event=message.get('event', ''), channel="plex") + eventItem = WebhookEventInfo(event=eventType, channel="plex") if message.get('Metadata'): if message.get('Metadata', {}).get('type') == 'episode': eventItem.item_type = "TV"