feat 同步删除插件增加Scripter X方式

This commit is contained in:
thsrite
2023-08-08 19:53:44 +08:00
parent d872efae3c
commit 5e6cf4d7d3
3 changed files with 333 additions and 121 deletions

View File

@ -83,10 +83,20 @@ class TransferHistory(Base):
return db.query(func.count(TransferHistory.id)).filter(TransferHistory.title.like(f'%{title}%')).first()[0] return db.query(func.count(TransferHistory.id)).filter(TransferHistory.title.like(f'%{title}%')).first()[0]
@staticmethod @staticmethod
def list_by(db: Session, mtype: str, title: str, year: int, season=None, episode=None): def list_by(db: Session, mtype: str = None, title: str = None, year: int = None, season: str = None,
episode: str = None, tmdbid: str = None):
""" """
据tmdbid、season、season_episode查询转移记录 据tmdbid、season、season_episode查询转移记录
""" """
if tmdbid and not season and not episode:
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid).all()
if tmdbid and season and not episode:
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid,
TransferHistory.seasons == season).all()
if tmdbid and season and episode:
return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid,
TransferHistory.seasons == season,
TransferHistory.episodes == episode).all()
# 电视剧所有季集|电影 # 电视剧所有季集|电影
if not season and not episode: if not season and not episode:
return db.query(TransferHistory).filter(TransferHistory.type == mtype, return db.query(TransferHistory).filter(TransferHistory.type == mtype,

View File

@ -43,8 +43,8 @@ class TransferHistoryOper(DbOper):
""" """
return TransferHistory.statistic(self._db, days) return TransferHistory.statistic(self._db, days)
def get_by(self, mtype: str, title: str, year: int, def get_by(self, mtype: str = None, title: str = None, year: int = None,
season: str = None, episode: str = None) -> Any: season: str = None, episode: str = None, tmdbid: str = None) -> Any:
""" """
按类型、标题、年份、季集查询转移记录 按类型、标题、年份、季集查询转移记录
""" """
@ -53,7 +53,8 @@ class TransferHistoryOper(DbOper):
title=title, title=title,
year=year, year=year,
season=season, season=season,
episode=episode) episode=episode,
tmdbid=tmdbid)
def delete(self, historyid): def delete(self, historyid):
""" """

View File

@ -15,6 +15,7 @@ from app.db.transferhistory_oper import TransferHistoryOper
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.themoviedb.tmdbv3api import Episode
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.schemas.types import NotificationType, EventType from app.schemas.types import NotificationType, EventType
@ -44,15 +45,17 @@ class MediaSyncDel(_PluginBase):
# 私有属性 # 私有属性
_scheduler: Optional[BackgroundScheduler] = None _scheduler: Optional[BackgroundScheduler] = None
_enabled = False _enabled = False
_sync_type: str = ""
_cron: str = "" _cron: str = ""
_notify = False _notify = False
_del_source = False _del_source = False
_exclude_path = None _exclude_path = None
_episode = None
_transferhis = None _transferhis = None
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
self._transferhis = TransferHistoryOper() self._transferhis = TransferHistoryOper()
self.episode = Episode()
# 停止现有任务 # 停止现有任务
self.stop_service() self.stop_service()
@ -60,6 +63,7 @@ class MediaSyncDel(_PluginBase):
# 读取配置 # 读取配置
if config: if config:
self._enabled = config.get("enabled") self._enabled = config.get("enabled")
self._sync_type = config.get("sync_type")
self._cron = config.get("cron") self._cron = config.get("cron")
self._notify = config.get("notify") self._notify = config.get("notify")
self._del_source = config.get("del_source") self._del_source = config.get("del_source")
@ -69,16 +73,23 @@ class MediaSyncDel(_PluginBase):
self._scheduler = BackgroundScheduler(timezone=settings.TZ) self._scheduler = BackgroundScheduler(timezone=settings.TZ)
if self._cron: if self._cron:
try: try:
self._scheduler.add_job(func=self.sync_del, if str(self._sync_type) == "log":
trigger=CronTrigger.from_crontab(self._cron), self._scheduler.add_job(func=self.sync_del_by_log,
name="媒体库同步删除") trigger=CronTrigger.from_crontab(self._cron),
name="媒体库同步删除")
if str(self._sync_type) == "plugin":
self._scheduler.add_job(func=self.sync_del_by_plugin,
trigger=CronTrigger.from_crontab(self._cron),
name="媒体库同步删除")
except Exception as err: except Exception as err:
logger.error(f"定时任务配置错误:{err}") logger.error(f"定时任务配置错误:{err}")
# 推送实时消息 # 推送实时消息
self.systemmessage.put(f"执行周期配置错误:{err}") self.systemmessage.put(f"执行周期配置错误:{err}")
else: else:
self._scheduler.add_job(self.sync_del, "interval", minutes=30, name="媒体库同步删除") if str(self._sync_type) == "log":
self._scheduler.add_job(self.sync_del_by_log, "interval", minutes=30, name="媒体库同步删除")
if str(self._sync_type) == "plugin":
self._scheduler.add_job(self.sync_del_by_plugin, "interval", minutes=30, name="媒体库同步删除")
# 启动任务 # 启动任务
if self._scheduler.get_jobs(): if self._scheduler.get_jobs():
self._scheduler.print_jobs() self._scheduler.print_jobs()
@ -105,115 +116,150 @@ class MediaSyncDel(_PluginBase):
拼装插件配置页面需要返回两块数据1、页面配置2、数据结构 拼装插件配置页面需要返回两块数据1、页面配置2、数据结构
""" """
return [ return [
{ {
'component': 'VForm', 'component': 'VForm',
'content': [ 'content': [
{ {
'component': 'VRow', 'component': 'VRow',
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 6 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'enabled', 'model': 'enabled',
'label': '启用插件', 'label': '启用插件',
} }
} }
] ]
}, },
{ {
'component': 'VCol', 'component': 'VCol',
'props': { 'props': {
'cols': 12, 'cols': 12,
'md': 6 'md': 4
}, },
'content': [ 'content': [
{ {
'component': 'VSwitch', 'component': 'VSwitch',
'props': { 'props': {
'model': 'notify', 'model': 'notify',
'label': '发送通知', 'label': '发送通知',
} }
} }
] ]
} },
] {
}, 'component': 'VCol',
{ 'props': {
'component': 'VRow', 'cols': 12,
'content': [ 'md': 4
{ },
'component': 'VCol', 'content': [
'props': { {
'cols': 12, 'component': 'VSwitch',
'md': 6 'props': {
}, 'model': 'del_source',
'content': [ 'label': '删除源文件',
{ }
'component': 'VSwitch', }
'props': { ]
'model': 'del_source', }
'label': '删除源文件', ]
} },
} {
] 'component': 'VRow',
} 'content': [
] {
}, 'component': 'VCol',
{ 'props': {
'component': 'VRow', 'cols': 12,
'content': [ 'md': 4
{ },
'component': 'VCol', 'content': [
'props': { {
'cols': 12, 'component': 'VSelect',
'md': 6 'props': {
}, 'model': 'sync_type',
'content': [ 'label': '同步方式',
{ 'items': [
'component': 'VTextField', {'title': '日志', 'value': 'log'},
'props': { {'title': 'Scripter X', 'value': 'plugin'}
'model': 'cron', ]
'label': '执行周期', }
'placeholder': '5位cron表达式留空自动' }
} ]
} },
] {
}, 'component': 'VCol',
{ 'props': {
'component': 'VCol', 'cols': 12,
'props': { 'md': 4
'cols': 12, },
'md': 6 'content': [
}, {
'content': [ 'component': 'VTextField',
{ 'props': {
'component': 'VTextField', 'model': 'cron',
'props': { 'label': '执行周期',
'model': 'exclude_path', 'placeholder': '5位cron表达式留空自动'
'label': '排除路径' }
} }
} ]
] },
} {
] 'component': 'VCol',
}, 'props': {
'cols': 12,
] 'md': 4
} },
], { 'content': [
"enabled": False, {
"notify": True, 'component': 'VTextField',
"del_source": False, 'props': {
"cron": "*/30 * * * *", 'model': 'exclude_path',
"exclude_path": "", 'label': '排除路径'
} }
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VAlert',
'props': {
'text': '同步方式分为日志同步和Scripter X。日志同步需要配置执行周期默认30分钟执行一次。'
'Scripter X方式需要emby安装并配置Scripter X插件无需配置执行周期。'
}
}
]
}
]
}
]
}
], {
"enabled": False,
"notify": True,
"del_source": False,
"sync_type": "log",
"cron": "*/30 * * * *",
"exclude_path": "",
}
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
""" """
@ -341,9 +387,165 @@ class MediaSyncDel(_PluginBase):
} }
] ]
def sync_del(self): @eventmanager.register(EventType.WebhookMessage)
def sync_del_by_plugin(self, event):
""" """
emby删除媒体库同步删除历史记录 emby删除媒体库同步删除历史记录
Scripter X插件
"""
if not self._enabled:
return
event_data = event.event_data
event_type = event_data.get("event_type")
if not event_type or str(event_type) != 'media_del':
return
# 是否虚拟标识
item_isvirtual = event_data.get("item_isvirtual")
if not item_isvirtual:
logger.error("item_isvirtual参数未配置为防止误删除暂停插件运行")
self.update_config({
"enable": False,
"del_source": self._del_source,
"exclude_path": self._exclude_path,
"notify": self._notify,
"cron": self._cron,
"sync_type": self._sync_type,
})
return
# 如果是虚拟item则直接return不进行删除
if item_isvirtual == 'True':
return
# 读取历史记录
history = self.get_data('history') or []
# 媒体类型
media_type = event_data.get("media_type")
# 媒体名称
media_name = event_data.get("media_name")
# 媒体路径
media_path = event_data.get("media_path")
# tmdb_id
tmdb_id = event_data.get("tmdb_id")
# 季数
season_num = event_data.get("season_num")
if season_num and str(season_num).isdigit() and int(season_num) < 10:
season_num = f'S0{season_num}'
else:
season_num = f'S{season_num}'
# 集数
episode_num = event_data.get("episode_num")
if episode_num and str(episode_num).isdigit() and int(episode_num) < 10:
episode_num = f'E0{episode_num}'
else:
episode_num = f'E{episode_num}'
if not media_type:
logger.error(f"{media_name} 同步删除失败,未获取到媒体类型")
return
if not tmdb_id or not str(tmdb_id).isdigit():
logger.error(f"{media_name} 同步删除失败未获取到TMDB ID")
return
if self._exclude_path and media_path and any(
os.path.abspath(media_path).startswith(os.path.abspath(path)) for path in
self._exclude_path.split(",")):
logger.info(f"媒体路径 {media_path} 已被排除,暂不处理")
return
# 删除电影
if media_type == "Movie":
msg = f'电影 {media_name} {tmdb_id}'
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id)
# 删除电视剧
elif media_type == "Series":
msg = f'剧集 {media_name} {tmdb_id}'
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id)
# 删除季 S02
elif media_type == "Season":
if not season_num or not str(season_num).isdigit():
logger.error(f"{media_name} 季同步删除失败,未获取到具体季")
return
msg = f'剧集 {media_name} S{season_num} {tmdb_id}'
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id,
season=season_num)
# 删除剧集S02E02
elif media_type == "Episode":
if not season_num or not str(season_num).isdigit() or not episode_num or not str(episode_num).isdigit():
logger.error(f"{media_name} 集同步删除失败,未获取到具体集")
return
msg = f'剧集 {media_name} S{season_num}E{episode_num} {tmdb_id}'
transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id,
season=season_num,
episode=episode_num)
else:
return
logger.info(f"正在同步删除{msg}")
if not transfer_history:
logger.warn(f"{media_type} {media_name} 未获取到可删除数据")
return
# 开始删除
del_cnt = 0
image = 'https://emby.media/notificationicon.png'
year = None
for transferhis in transfer_history:
image = transferhis.image
year = transferhis.year
if media_type == "Episode" or media_type == "Movie":
# 如果有剧集或者电影有多个版本的话,需要根据名称筛选下要删除的版本
if os.path.basename(transferhis.dest) != os.path.basename(media_path):
continue
self._transferhis.delete(transferhis.id)
del_cnt += 1
# 删除种子任务
if self._del_source and transferhis.download_hash:
self.chain.remove_torrents(transferhis.download_hash)
logger.info(f"同步删除 {msg} 完成!")
# 发送消息
if self._notify:
if media_type == "Episode":
# 根据tmdbid获取图片
image = self._episode().images(tv_id=tmdb_id,
season_id=season_num,
episode_id=episode_num,
orginal=True)
# 发送通知
self.post_message(
mtype=NotificationType.MediaServer,
title="媒体库同步删除任务完成",
image=image,
text=f"{msg}\n"
f"数量 {del_cnt}\n"
f"时间 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}"
)
history.append({
"type": "电影" if media_type == "Movie" else "电视剧",
"title": media_name,
"year": year,
"path": media_path,
"season": season_num,
"episode": episode_num,
"image": image,
"del_time": str(datetime.datetime.now())
})
# 保存历史
self.save_data("history", history)
self.save_data("last_time", datetime.datetime.now())
def sync_del_by_log(self):
"""
emby删除媒体库同步删除历史记录
日志方式
""" """
# 读取历史记录 # 读取历史记录
history = self.get_data('history') or [] history = self.get_data('history') or []
@ -445,7 +647,6 @@ class MediaSyncDel(_PluginBase):
# 发送消息 # 发送消息
if self._notify: if self._notify:
self.post_message( self.post_message(
mtype=NotificationType.MediaServer,
title="媒体库同步删除任务完成", title="媒体库同步删除任务完成",
text=f"{msg}\n" text=f"{msg}\n"
f"数量 {len(transfer_history)}\n" f"数量 {len(transfer_history)}\n"