feat BestFilmVersion 新增支持webhook
This commit is contained in:
parent
99be66b35f
commit
792aef943b
@ -373,7 +373,10 @@ class Jellyfin(metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
eventItem = WebhookEventInfo(
|
eventItem = WebhookEventInfo(
|
||||||
event=message.get('NotificationType', ''),
|
event=message.get('NotificationType', ''),
|
||||||
|
item_id=message.get('ItemId'),
|
||||||
item_name=message.get('Name'),
|
item_name=message.get('Name'),
|
||||||
|
item_type=message.get('ItemType'),
|
||||||
|
tmdb_id=message.get('Provider_tmdb'),
|
||||||
user_name=message.get('NotificationUsername'),
|
user_name=message.get('NotificationUsername'),
|
||||||
channel="jellyfin"
|
channel="jellyfin"
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Any, List, Dict, Tuple
|
from typing import Optional, Any, List, Dict, Tuple
|
||||||
|
from xml.dom.minidom import parseString
|
||||||
|
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
from apscheduler.triggers.cron import CronTrigger
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
@ -9,11 +10,15 @@ from requests import Response
|
|||||||
from app.chain.subscribe import SubscribeChain
|
from app.chain.subscribe import SubscribeChain
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.context import MediaInfo
|
from app.core.context import MediaInfo
|
||||||
|
from app.core.event import eventmanager
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules.emby import Emby
|
from app.modules.emby import Emby
|
||||||
from app.modules.jellyfin import Jellyfin
|
from app.modules.jellyfin import Jellyfin
|
||||||
|
from app.modules.plex import Plex
|
||||||
from app.plugins import _PluginBase
|
from app.plugins import _PluginBase
|
||||||
from app.schemas.types import MediaType
|
from app.schemas import WebhookEventInfo
|
||||||
|
from app.schemas.types import MediaType, EventType
|
||||||
|
from app.utils.http import RequestUtils
|
||||||
|
|
||||||
|
|
||||||
class BestFilmVersion(_PluginBase):
|
class BestFilmVersion(_PluginBase):
|
||||||
@ -47,6 +52,8 @@ class BestFilmVersion(_PluginBase):
|
|||||||
_enabled: bool = False
|
_enabled: bool = False
|
||||||
_cron: str = ""
|
_cron: str = ""
|
||||||
_notify: bool = False
|
_notify: bool = False
|
||||||
|
_webhook_enabled: bool = False
|
||||||
|
_only_once: bool = False
|
||||||
|
|
||||||
def init_plugin(self, config: dict = None):
|
def init_plugin(self, config: dict = None):
|
||||||
self._cache_path = settings.TEMP_PATH / "__best_film_version_cache__"
|
self._cache_path = settings.TEMP_PATH / "__best_film_version_cache__"
|
||||||
@ -60,8 +67,10 @@ class BestFilmVersion(_PluginBase):
|
|||||||
self._enabled = config.get("enabled")
|
self._enabled = config.get("enabled")
|
||||||
self._cron = config.get("cron")
|
self._cron = config.get("cron")
|
||||||
self._notify = config.get("notify")
|
self._notify = config.get("notify")
|
||||||
|
self._webhook_enabled = config.get("webhook_enabled")
|
||||||
|
self._only_once = config.get("only_once")
|
||||||
|
|
||||||
if self._enabled:
|
if self._enabled and not self._webhook_enabled:
|
||||||
|
|
||||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||||
if self._cron:
|
if self._cron:
|
||||||
@ -81,6 +90,17 @@ class BestFilmVersion(_PluginBase):
|
|||||||
self._scheduler.print_jobs()
|
self._scheduler.print_jobs()
|
||||||
self._scheduler.start()
|
self._scheduler.start()
|
||||||
|
|
||||||
|
if self._enabled and self._only_once:
|
||||||
|
self._only_once = False
|
||||||
|
self.update_config({
|
||||||
|
"enabled": self._enabled,
|
||||||
|
"cron": self._cron,
|
||||||
|
"notify": self._notify,
|
||||||
|
"webhook_enabled": self._webhook_enabled,
|
||||||
|
"only_once": self._only_once
|
||||||
|
})
|
||||||
|
self.sync()
|
||||||
|
|
||||||
def get_state(self) -> bool:
|
def get_state(self) -> bool:
|
||||||
return self._enabled
|
return self._enabled
|
||||||
|
|
||||||
@ -115,7 +135,7 @@ class BestFilmVersion(_PluginBase):
|
|||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12,
|
'cols': 12,
|
||||||
'md': 6
|
'md': 3
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
@ -131,7 +151,7 @@ class BestFilmVersion(_PluginBase):
|
|||||||
'component': 'VCol',
|
'component': 'VCol',
|
||||||
'props': {
|
'props': {
|
||||||
'cols': 12,
|
'cols': 12,
|
||||||
'md': 6
|
'md': 3
|
||||||
},
|
},
|
||||||
'content': [
|
'content': [
|
||||||
{
|
{
|
||||||
@ -142,6 +162,55 @@ class BestFilmVersion(_PluginBase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12,
|
||||||
|
'md': 3
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VSwitch',
|
||||||
|
'props': {
|
||||||
|
'model': 'only_once',
|
||||||
|
'label': '立即运行一次',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12,
|
||||||
|
'md': 3
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VSwitch',
|
||||||
|
'props': {
|
||||||
|
'model': 'webhook_enabled',
|
||||||
|
'label': 'webhook',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'component': 'VCol',
|
||||||
|
'props': {
|
||||||
|
'cols': 12,
|
||||||
|
},
|
||||||
|
'content': [
|
||||||
|
{
|
||||||
|
'component': 'VSwitch',
|
||||||
|
'props': {
|
||||||
|
'model': 'test',
|
||||||
|
'label': '假开关,用来描述webhook选项,插件支持主动拉获取媒体库数据和webhook两种方式,两者只能选一,'
|
||||||
|
'不知道webhook的,默认就好. Plex用户,使用主动获取时,执行周期设置大些, 建议大于1小时,'
|
||||||
|
'收藏api接口,只能走的plex官网,有接口限制'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -172,6 +241,9 @@ class BestFilmVersion(_PluginBase):
|
|||||||
"enabled": False,
|
"enabled": False,
|
||||||
"notify": False,
|
"notify": False,
|
||||||
"cron": "*/30 * * * *",
|
"cron": "*/30 * * * *",
|
||||||
|
"webhook_enabled": False,
|
||||||
|
"only_once": False,
|
||||||
|
"test": False
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_page(self) -> List[dict]:
|
def get_page(self) -> List[dict]:
|
||||||
@ -300,10 +372,18 @@ class BestFilmVersion(_PluginBase):
|
|||||||
# 读取历史记录
|
# 读取历史记录
|
||||||
history = self.get_data('history') or []
|
history = self.get_data('history') or []
|
||||||
|
|
||||||
|
all_item = []
|
||||||
# 读取收藏
|
# 读取收藏
|
||||||
if settings.MEDIASERVER == 'jellyfin':
|
if settings.MEDIASERVER == 'jellyfin':
|
||||||
|
# 获取所有user
|
||||||
|
users_url = "{HOST}Users?&apikey={APIKEY}"
|
||||||
|
users = self.get_users(Jellyfin().get_data(users_url))
|
||||||
|
if not users:
|
||||||
|
logger.info(f"bestfilmversion/users_url: {users_url}")
|
||||||
|
return
|
||||||
|
for user in users:
|
||||||
# 根据加入日期 降序排序
|
# 根据加入日期 降序排序
|
||||||
url = "{HOST}Users/{USER}/Items?SortBy=DateCreated%2CSortName" \
|
url = "{HOST}Users/" + user + "/Items?SortBy=DateCreated%2CSortName" \
|
||||||
"&SortOrder=Descending" \
|
"&SortOrder=Descending" \
|
||||||
"&Filters=IsFavorite" \
|
"&Filters=IsFavorite" \
|
||||||
"&Recursive=true" \
|
"&Recursive=true" \
|
||||||
@ -314,9 +394,16 @@ class BestFilmVersion(_PluginBase):
|
|||||||
"&Limit=20" \
|
"&Limit=20" \
|
||||||
"&apikey={APIKEY}"
|
"&apikey={APIKEY}"
|
||||||
resp = self.get_items(Jellyfin().get_data(url))
|
resp = self.get_items(Jellyfin().get_data(url))
|
||||||
|
all_item.extend(resp)
|
||||||
elif settings.MEDIASERVER == 'emby':
|
elif settings.MEDIASERVER == 'emby':
|
||||||
|
# 获取所有user
|
||||||
|
get_users_url = "{HOST}Users?&api_key={APIKEY}"
|
||||||
|
users = self.get_users(Jellyfin().get_data(get_users_url))
|
||||||
|
if not users:
|
||||||
|
return
|
||||||
|
for user in users:
|
||||||
# 根据加入日期 降序排序
|
# 根据加入日期 降序排序
|
||||||
url = "{HOST}emby/Users/{USER}/Items?SortBy=DateCreated%2CSortName" \
|
url = "{HOST}emby/Users/" + user + "/Items?SortBy=DateCreated%2CSortName" \
|
||||||
"&SortOrder=Descending" \
|
"&SortOrder=Descending" \
|
||||||
"&Filters=IsFavorite" \
|
"&Filters=IsFavorite" \
|
||||||
"&Recursive=true" \
|
"&Recursive=true" \
|
||||||
@ -326,11 +413,12 @@ class BestFilmVersion(_PluginBase):
|
|||||||
"&EnableTotalRecordCount=false" \
|
"&EnableTotalRecordCount=false" \
|
||||||
"&Limit=20&api_key={APIKEY}"
|
"&Limit=20&api_key={APIKEY}"
|
||||||
resp = self.get_items(Emby().get_data(url))
|
resp = self.get_items(Emby().get_data(url))
|
||||||
|
all_item.extend(resp)
|
||||||
else:
|
else:
|
||||||
# TODO plex待开发
|
resp = self.plex_get_watchlist(self)
|
||||||
return
|
all_item.extend(resp)
|
||||||
|
|
||||||
for data in resp:
|
for data in all_item:
|
||||||
# 检查缓存
|
# 检查缓存
|
||||||
if data.get('Name') in caches:
|
if data.get('Name') in caches:
|
||||||
continue
|
continue
|
||||||
@ -341,8 +429,7 @@ class BestFilmVersion(_PluginBase):
|
|||||||
elif settings.MEDIASERVER == 'emby':
|
elif settings.MEDIASERVER == 'emby':
|
||||||
item_info_resp = Emby().get_iteminfo(itemid=data.get('Id'))
|
item_info_resp = Emby().get_iteminfo(itemid=data.get('Id'))
|
||||||
else:
|
else:
|
||||||
# TODO plex待开发
|
item_info_resp = self.plex_get_iteminfo(itemid=data.get('Id'))
|
||||||
return
|
|
||||||
|
|
||||||
logger.info(f'BestFilmVersion插件 item打印 {item_info_resp}')
|
logger.info(f'BestFilmVersion插件 item打印 {item_info_resp}')
|
||||||
if not item_info_resp:
|
if not item_info_resp:
|
||||||
@ -401,3 +488,167 @@ class BestFilmVersion(_PluginBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(str(e))
|
print(str(e))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_users(resp: Response):
|
||||||
|
try:
|
||||||
|
if resp:
|
||||||
|
return [data['Id'] for data in resp.json()]
|
||||||
|
else:
|
||||||
|
logger.error(f"BestFilmVersion/Users 未获取到返回数据")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"连接BestFilmVersion/Users 出错:" + str(e))
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def plex_get_watchlist(self):
|
||||||
|
# 根据加入日期 降序排序
|
||||||
|
url = f"https://metadata.provider.plex.tv/library/sections/watchlist/all?type=1&sort=addedAt%3Adesc" \
|
||||||
|
f"&X-Plex-Container-Start=0&X-Plex-Container-Size=50" \
|
||||||
|
f"&X-Plex-Token={self.service_apikey}"
|
||||||
|
res = []
|
||||||
|
try:
|
||||||
|
resp = RequestUtils().get_res(url=url)
|
||||||
|
if resp:
|
||||||
|
dom = parseString(resp.text)
|
||||||
|
# 获取文档元素对象
|
||||||
|
elem = dom.documentElement
|
||||||
|
# 获取 指定元素
|
||||||
|
eles = elem.getElementsByTagName('Video')
|
||||||
|
for ele in eles:
|
||||||
|
data = {}
|
||||||
|
# 获取标签中内容
|
||||||
|
ele_id = ele.attributes['ratingKey'].nodeValue
|
||||||
|
ele_title = ele.attributes['title'].nodeValue
|
||||||
|
ele_type = ele.attributes['type'].nodeValue
|
||||||
|
_type = "Movie" if ele_type == "movie" else ""
|
||||||
|
data['Id'] = ele_id
|
||||||
|
data['Name'] = ele_title
|
||||||
|
data['Type'] = _type
|
||||||
|
res.append(data)
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
logger.error(f"Plex/Watchlist 未获取到返回数据")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"连接Plex/Watchlist 出错:" + str(e))
|
||||||
|
return []
|
||||||
|
|
||||||
|
def plex_get_iteminfo(self, itemid):
|
||||||
|
url = f"https://metadata.provider.plex.tv/library/metadata/{itemid}" \
|
||||||
|
f"?X-Plex-Token={settings.PLEX_TOKEN}"
|
||||||
|
try:
|
||||||
|
resp = RequestUtils().get_res(url=url)
|
||||||
|
if resp:
|
||||||
|
dom = parseString(resp.text)
|
||||||
|
# 获取文档元素对象
|
||||||
|
elem = dom.documentElement
|
||||||
|
# 获取 指定元素
|
||||||
|
eles = elem.getElementsByTagName('Video')
|
||||||
|
for ele in eles:
|
||||||
|
# 获取标签中内容
|
||||||
|
return {"ExternalUrls": "TheMovieDb", "Url": f"{self.ele_get_tmdbid(ele)}"}
|
||||||
|
else:
|
||||||
|
logger.error(f"Plex/Items 未获取到返回数据")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"连接Plex/Items 出错:" + str(e))
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ele_get_tmdbid(ele):
|
||||||
|
data = []
|
||||||
|
for h in ele.getElementsByTagName('Guid'):
|
||||||
|
tmdbid = h.attributes['id'].nodeValue if h.attributes['id'].nodeValue.__contains__("tmdb") else ""
|
||||||
|
if not tmdbid:
|
||||||
|
continue
|
||||||
|
obj = {"Name": "TheMovieDb", "Url": f"{tmdbid}"}
|
||||||
|
data.append(obj)
|
||||||
|
return data
|
||||||
|
logger.warn(f"连接Plex/Guid 警告:" + "未获取到tmdbid数据")
|
||||||
|
return data
|
||||||
|
|
||||||
|
@eventmanager.register(EventType.WebhookMessage)
|
||||||
|
def webhook_message_action(self, event):
|
||||||
|
|
||||||
|
if not self._enabled:
|
||||||
|
return
|
||||||
|
if not self._webhook_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
data: WebhookEventInfo = event.event_data
|
||||||
|
logger.info(f'BestFilmVersion/webhook_message_action WebhookEventInfo打印:{data}')
|
||||||
|
|
||||||
|
mediainfo: Optional[MediaInfo] = None
|
||||||
|
if not data.tmdb_id:
|
||||||
|
info = None
|
||||||
|
if data.channel == 'jellyfin' and data.event == 'UserDataSaved':
|
||||||
|
info = Jellyfin().get_iteminfo(itemid=data.item_id)
|
||||||
|
if data.channel == 'emby' and data.event == 'item.rate':
|
||||||
|
info = Emby().get_iteminfo(itemid=data.item_id)
|
||||||
|
if data.channel == 'plex' and data.event == 'item.rate':
|
||||||
|
info = Plex().get_iteminfo(itemid=data.item_id)
|
||||||
|
logger.info(f'BestFilmVersion/webhook_message_action item打印:{info}')
|
||||||
|
|
||||||
|
if not info:
|
||||||
|
return
|
||||||
|
if info['Type'] not in ['Movie', 'MOV', 'movie']:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取tmdb_id
|
||||||
|
media_info_ids = info.get('ExternalUrls')
|
||||||
|
for media_info_id in media_info_ids:
|
||||||
|
|
||||||
|
if 'TheMovieDb' != media_info_id.get('Name'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
tmdb_find_id = str(media_info_id.get('Url')).split('/')
|
||||||
|
tmdb_find_id.reverse()
|
||||||
|
tmdb_id = tmdb_find_id[0]
|
||||||
|
|
||||||
|
mediainfo = self.chain.recognize_media(tmdbid=tmdb_id, mtype=MediaType.MOVIE)
|
||||||
|
if not mediainfo:
|
||||||
|
logger.warn(f'未识别到媒体信息,标题:{data.item_name},tmdbID:{tmdb_id}')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if data.item_type not in ['Movie', 'MOV', 'movie']:
|
||||||
|
return
|
||||||
|
|
||||||
|
mediainfo = self.chain.recognize_media(tmdbid=data.tmdb_id, mtype=MediaType.MOVIE)
|
||||||
|
if not mediainfo:
|
||||||
|
logger.warn(f'未识别到媒体信息,标题:{data.item_name},tmdbID:{data.tmdb_id}')
|
||||||
|
return
|
||||||
|
|
||||||
|
# 读取缓存
|
||||||
|
caches = self._cache_path.read_text().split("\n") if self._cache_path.exists() else []
|
||||||
|
# 检查缓存
|
||||||
|
if mediainfo.title in caches:
|
||||||
|
return
|
||||||
|
# 读取历史记录
|
||||||
|
history = self.get_data('history') or []
|
||||||
|
# 添加订阅
|
||||||
|
self.subscribechain.add(mtype=MediaType.MOVIE,
|
||||||
|
title=mediainfo.title,
|
||||||
|
year=mediainfo.year,
|
||||||
|
tmdbid=mediainfo.tmdb_id,
|
||||||
|
best_version=True,
|
||||||
|
username="收藏洗版",
|
||||||
|
exist_ok=True)
|
||||||
|
# 加入缓存
|
||||||
|
caches.append(data.item_name)
|
||||||
|
# 存储历史记录
|
||||||
|
if mediainfo.tmdb_id not in [h.get("tmdbid") for h in history]:
|
||||||
|
history.append({
|
||||||
|
"title": mediainfo.title,
|
||||||
|
"type": mediainfo.type.value,
|
||||||
|
"year": mediainfo.year,
|
||||||
|
"poster": mediainfo.get_poster_image(),
|
||||||
|
"overview": mediainfo.overview,
|
||||||
|
"tmdbid": mediainfo.tmdb_id,
|
||||||
|
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
})
|
||||||
|
# 保存历史记录
|
||||||
|
self.save_data('history', history)
|
||||||
|
# 保存缓存
|
||||||
|
self._cache_path.write_text("\n".join(caches))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user