Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
"name": "订阅助手(增强版)",
"description": "多场景管理订阅,实现订阅全生命周期管理。",
"labels": "订阅",
"version": "0.6.7",
"version": "0.6.8",
"icon": "https://raw.githubusercontent.com/InfinityPacer/MoviePilot-Plugins/main/icons/subscribeassistantenhanced.png",
"author": "InfinityPacer",
"level": 1,
"system_version": ">2.14.1",
"history": {
"v0.6.8": "优化订阅通知图片策略,媒体通知优先使用订阅图片,无图消息改为纯文本并标注插件来源。",
"v0.6.7": "分集洗版转为全集洗版时保留已下载剧集、当前优先级和分集优先级,避免转换后订阅进度重置。",
"v0.6.6": "修正洗版订阅继承手动总集数的问题,补充分集转全集完成快照,并仅在已有订阅覆盖最新 TMDB 集数时完成自动纠错。",
"v0.6.5": "优化插件品牌图标与配置页视觉效果,提升界面一致性与操作辨识度。",
Expand Down
1 change: 1 addition & 0 deletions plugins.v2/subscribeassistantenhanced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

## 版本更新日志

- v0.6.8:优化订阅通知图片策略,媒体通知优先使用订阅图片,无图消息改为纯文本并标注插件来源。
- v0.6.7:分集洗版转为全集洗版时保留已下载剧集、当前优先级和分集优先级,避免转换后订阅进度重置。
- v0.6.6:修正洗版订阅继承手动总集数的问题,补充分集转全集完成快照,并仅在已有订阅覆盖最新 TMDB 集数时完成自动纠错。
- v0.6.5:优化插件品牌图标与配置页视觉效果,提升界面一致性与操作辨识度。
Expand Down
40 changes: 33 additions & 7 deletions plugins.v2/subscribeassistantenhanced/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class SubscribeAssistantEnhanced(_PluginBase):
# 插件图标
plugin_icon = "https://raw.githubusercontent.com/InfinityPacer/MoviePilot-Plugins/main/icons/subscribeassistantenhanced.png"
# 插件版本
plugin_version = "0.6.7"
plugin_version = "0.6.8"
_site_cache_candidate_helper_warned = False
# 插件作者
plugin_author = "InfinityPacer"
Expand Down Expand Up @@ -244,6 +244,7 @@ def _init_modules(self):
retention_days=cfg.verify_retention_days,
notify_fn=self._notify_subscribe,
rebuild_subscribe_fn=self._rebuild_subscribe_from_snapshot,
get_subscribe_image_fn=self._get_subscribe_image,
)
priority_manager = PriorityManager(
tm.read,
Expand All @@ -259,6 +260,7 @@ def _init_modules(self):
restore_fn=self._restore_subscribe_from_snapshot,
snapshot_fn=verifier.snapshot,
format_desc_fn=lambda subscribe, mediainfo: self._format_subscribe_desc(subscribe, mediainfo),
notification_image_fn=self._resolve_notification_image,
plugin_name=self.plugin_name,
)
pending_refresh = PendingRefresh()
Expand Down Expand Up @@ -423,6 +425,7 @@ def _init_modules(self):
notify_fn=self._notify_subscribe,
related_downloads_fn=self._related_download_histories,
best_version_type=cfg.best_version_type,
notification_image_fn=self._resolve_notification_image,
plugin_name=self.plugin_name,
)
subscription_cleanup = SubscriptionCleanup(
Expand Down Expand Up @@ -454,6 +457,7 @@ def _init_modules(self):
subscribe_oper=self._subscribe_oper,
post_message=self.post_message,
notify_fn=self._notify_subscribe,
notification_image_fn=self._resolve_notification_image,
plugin_name=self.plugin_name,
deletes_store=deletes_store if cfg.download_monitor_enabled else None,
skip_deletion=cfg.skip_deletion,
Expand Down Expand Up @@ -675,7 +679,7 @@ def _run_backfill_now(self):
f"跳过 {results['skipped']} 个,累计补写 {results['filled_episodes']} 集"
)
self._notify_subscribe(
"洗版订阅下载事实回填",
"洗版下载事实回填完成",
action=(
f"扫描 {results['scanned']} 个订阅,成功回填 {results['updated']} 个,"
f"跳过 {results['skipped']} 个,累计补写 {results['filled_episodes']} 集"
Expand Down Expand Up @@ -759,7 +763,7 @@ def run_best_version_check(self):
self._notify_subscribe(
f"{format_subscribe(subscribe)} {mode_label}超过时限"
f"({self._best_version_timeout_days(subscribe)}天),已标记洗版优先级为完成",
image=mediainfo.get_message_image(),
image=self._resolve_notification_image(subscribe, mediainfo),
)
continue
if (
Expand Down Expand Up @@ -1641,7 +1645,7 @@ def _send_no_download_notification(self, subscribe, mediainfo, action: str,
score=mediainfo.vote_average,
user=subscribe.username,
reason=reason or "上映后超期且无下载",
image=mediainfo.get_message_image(),
image=self._resolve_notification_image(subscribe, mediainfo),
link="#/subscribe/tv?tab=mysub" if subscribe.type == "电视剧" else "#/subscribe/movie?tab=mysub",
)

Expand All @@ -1656,7 +1660,7 @@ def _send_subscribe_status_notification(self, subscribe, title_suffix: str,
score=mediainfo.vote_average if mediainfo else None,
user=subscribe.username,
reason=detail,
image=mediainfo.get_message_image() if mediainfo else None,
image=self._resolve_notification_image(subscribe, mediainfo),
link="#/subscribe/tv?tab=mysub" if media_type == "电视剧" else "#/subscribe/movie?tab=mysub",
)

Expand All @@ -1682,8 +1686,23 @@ def _notify_subscribe(self, title, text=None, image=None, link=None,
follow_up=follow_up if follow_up is not None else next_step,
diagnostic=diagnostic,
)
image = image or self.plugin_icon
self.post_message(mtype=NotificationType.Subscribe, title=title, text=text, image=image, link=link)
message_options = {}
if not image:
text = self._append_notification_source(text)
message_options["disable_web_page_preview"] = True
self.post_message(
mtype=NotificationType.Subscribe,
title=title,
text=text,
image=image or None,
link=link,
**message_options,
)

def _append_notification_source(self, text: Optional[str]) -> str:
"""无图消息追加插件来源,便于在通知转发和多插件场景中识别发送方。"""
source = f"来源:{self.plugin_name}"
return f"{text}\n\n{source}" if text else source

@staticmethod
def _format_notification_text(text=None, score=None, user=None, reason=None,
Expand Down Expand Up @@ -1717,3 +1736,10 @@ def _get_subscribe_image(subscribe):
if subscribe.poster:
return subscribe.poster.replace("original", "w500")
return ""

def _resolve_notification_image(self, subscribe=None, mediainfo=None):
"""解析媒体通知图片,优先保持订阅卡片当前展示图片。"""
subscribe_image = self._get_subscribe_image(subscribe) if subscribe else ""
if subscribe_image:
return subscribe_image
return mediainfo.get_message_image() if mediainfo else None
25 changes: 16 additions & 9 deletions plugins.v2/subscribeassistantenhanced/best_version/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class BestVersionConverter:

def __init__(self, subscribe_oper=None, clear_tasks_fn=None, send_event_fn=None,
notify_fn=None, restore_fn=None, snapshot_fn=None, format_desc_fn=None,
notification_image_fn=None,
plugin_name: str = "订阅助手(增强版)"):
"""注入订阅写库、任务清理、事件、通知、完成快照和失败恢复依赖。"""
self._subscribe_oper = subscribe_oper
Expand All @@ -28,6 +29,7 @@ def __init__(self, subscribe_oper=None, clear_tasks_fn=None, send_event_fn=None,
self._restore = restore_fn
self._snapshot = snapshot_fn
self._format_desc = format_desc_fn
self._notification_image = notification_image_fn
self._plugin_name = plugin_name

def convert_to_full(self, subscribe, mediainfo=None) -> bool:
Expand All @@ -45,7 +47,7 @@ def convert_to_full(self, subscribe, mediainfo=None) -> bool:
self._snapshot(subscribe=subscribe, mediainfo=mediainfo, scope=None)
except Exception as err:
logger.error(f"{subscribe_desc} 原因=登记完成快照失败,处理=停止转全集处理,错误={err}")
self._notify_failure(subscribe_desc, str(err), mediainfo=mediainfo)
self._notify_failure(subscribe, subscribe_desc, str(err), mediainfo=mediainfo)
return False

try:
Expand All @@ -56,7 +58,7 @@ def convert_to_full(self, subscribe, mediainfo=None) -> bool:
except Exception as err:
self._remove_history_snapshot(subscribe_dict)
logger.error(f"{subscribe_desc} 原因=删除分集洗版订阅失败,处理=停止转全集处理,错误={err}")
self._notify_failure(subscribe_desc, str(err), mediainfo=mediainfo)
self._notify_failure(subscribe, subscribe_desc, str(err), mediainfo=mediainfo)
return False

try:
Expand All @@ -67,7 +69,7 @@ def convert_to_full(self, subscribe, mediainfo=None) -> bool:
if new_sid:
logger.info(f"{subscribe_desc} 原因=分集洗版集数已符合目标集数,处理=已转为全集洗版订阅 (ID: {new_sid})")
self._send_subscribe_added(new_sid, mediainfo)
self._notify_success(subscribe_desc, mediainfo)
self._notify_success(subscribe, subscribe_desc, mediainfo)
return True

restored = self._restore(subscribe_dict, mediainfo) if self._restore else False
Expand All @@ -76,7 +78,7 @@ def convert_to_full(self, subscribe, mediainfo=None) -> bool:
f"错误信息={err_msg},分集订阅重建状态={restored}"
)
restore_text = "分集洗版订阅已尝试重建" if restored else "分集洗版订阅重建失败,请手动检查"
self._notify_failure(subscribe_desc, f"{err_msg}\n{restore_text}", mediainfo=mediainfo)
self._notify_failure(subscribe, subscribe_desc, f"{err_msg}\n{restore_text}", mediainfo=mediainfo)
return False

def _build_full_payload(self, subscribe_dict: dict) -> dict:
Expand Down Expand Up @@ -109,19 +111,18 @@ def _send_subscribe_added(self, sid, mediainfo):
"mediainfo": media_payload,
})

def _notify_success(self, subscribe_desc: str, mediainfo):
def _notify_success(self, subscribe, subscribe_desc: str, mediainfo):
"""发送转全集成功通知。"""
if not self._notify:
return
self._notify(
f"{subscribe_desc} 分集洗版集数已符合目标集数,已从分集洗版转为全集洗版订阅",
score=mediainfo.vote_average,
user=self._plugin_name,
image=mediainfo.get_message_image(),
image=self._resolve_notification_image(subscribe, mediainfo),
link="#/subscribe/tv?tab=mysub",
)

def _notify_failure(self, subscribe_desc: str, text: str, mediainfo=None):
def _notify_failure(self, subscribe, subscribe_desc: str, text: str, mediainfo=None):
"""发送转全集失败通知。"""
if not self._notify:
return
Expand All @@ -130,9 +131,15 @@ def _notify_failure(self, subscribe_desc: str, text: str, mediainfo=None):
text=text,
follow_up="请检查订阅状态",
diagnostic=True,
image=mediainfo.get_message_image() if mediainfo else None,
image=self._resolve_notification_image(subscribe, mediainfo),
)

def _resolve_notification_image(self, subscribe, mediainfo=None):
"""解析转全集通知图片;未注入统一解析器时沿用媒体图片。"""
if self._notification_image:
return self._notification_image(subscribe, mediainfo)
return mediainfo.get_message_image() if mediainfo else None

def _remove_history_snapshot(self, subscribe_dict: dict):
"""删除刚写入的完成历史,避免删除失败后同时存在活动订阅和完成记录。"""
remover = getattr(self._subscribe_oper, "remove_history", None)
Expand Down
15 changes: 11 additions & 4 deletions plugins.v2/subscribeassistantenhanced/best_version/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def __init__(self, priority_manager: PriorityManager,
notify_fn: Optional[Callable] = None,
related_downloads_fn: Optional[Callable] = None,
best_version_type: str = "no",
notification_image_fn: Optional[Callable] = None,
plugin_name: str = "订阅助手(增强版)"):
"""注入洗版编排依赖与自动洗版范围。"""
self._priority = priority_manager
Expand All @@ -30,6 +31,7 @@ def __init__(self, priority_manager: PriorityManager,
self._notify = notify_fn
self._related_downloads = related_downloads_fn
self._best_version_type = best_version_type
self._notification_image = notification_image_fn
self._plugin_name = plugin_name

def build_payload(self, subscribe) -> dict:
Expand Down Expand Up @@ -70,7 +72,7 @@ def start_best_version(self, subscribe, mediainfo):
if self._notify:
self._notify(
f"{format_subscribe_desc(subscribe)} 已达顶档,跳过洗版订阅",
image=mediainfo.get_message_image(),
image=self._resolve_notification_image(subscribe, mediainfo),
link="#/subscribe/movie?tab=mysub",
)
return None
Expand Down Expand Up @@ -113,8 +115,7 @@ def start_best_version(self, subscribe, mediainfo):
self._notify(
f"{format_subscribe_desc(subscribe)} 已添加{mode_label}订阅",
score=mediainfo.vote_average,
user=self._plugin_name,
image=mediainfo.get_message_image(),
image=self._resolve_notification_image(subscribe, mediainfo),
link="#/subscribe/movie?tab=mysub" if is_movie else "#/subscribe/tv?tab=mysub",
)
elif self._notify:
Expand All @@ -127,10 +128,16 @@ def start_best_version(self, subscribe, mediainfo):
reason=err_msg,
follow_up="请检查订阅创建错误",
diagnostic=True,
image=mediainfo.get_message_image(),
image=self._resolve_notification_image(subscribe, mediainfo),
)
return sid

def _resolve_notification_image(self, subscribe, mediainfo):
"""解析洗版通知图片;未注入统一解析器时沿用媒体图片。"""
if self._notification_image:
return self._notification_image(subscribe, mediainfo)
return mediainfo.get_message_image()

@staticmethod
def _mode_label(subscribe) -> str:
"""按订阅实际洗版形态返回用户可见标签。"""
Expand Down
24 changes: 17 additions & 7 deletions plugins.v2/subscribeassistantenhanced/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,20 @@ def on_subscribe_complete(self, event):
subscribe = SimpleNamespace(**subscribe_info)
detail(f"订阅完成事件:{format_subscribe_label(subscribe, subscribe_id)}")

mediainfo_from_dict = self.get("mediainfo_from_dict")
mediainfo = None
if mediainfo_from_dict:
try:
mediainfo = mediainfo_from_dict(data.get("mediainfo"))
except Exception:
logger.warning(
f"订阅完成事件:{format_subscribe_label(subscribe, subscribe_id)} "
"媒体信息解析失败,按无媒体信息继续保存快照并清理任务"
)

verifier = self.get("verifier")
if subscribe and verifier:
verifier.snapshot(subscribe=subscribe, mediainfo=None, scope=None)
verifier.snapshot(subscribe=subscribe, mediainfo=mediainfo, scope=None)

task_manager = self.get("task_manager")
if subscribe_id and task_manager:
Expand All @@ -306,12 +317,9 @@ def on_subscribe_complete(self, event):

# 自动洗版创建(按开关;mediainfo 由事件重建,洗版编排判断是否新建洗版订阅)
orchestrator = self.get("orchestrator")
mediainfo_from_dict = self.get("mediainfo_from_dict")
if orchestrator and mediainfo_from_dict:
mediainfo = mediainfo_from_dict(data.get("mediainfo"))
if mediainfo:
detail(f"订阅完成:{format_subscribe(subscribe)} 检查是否需要自动创建洗版订阅")
orchestrator.start_best_version(subscribe, mediainfo)
if orchestrator and mediainfo:
detail(f"订阅完成:{format_subscribe(subscribe)} 检查是否需要自动创建洗版订阅")
orchestrator.start_best_version(subscribe, mediainfo)

def on_transfer_intercept(self, event):
"""TransferIntercept → 订阅清理目标文件删除。"""
Expand Down Expand Up @@ -532,6 +540,7 @@ def _notify_download_resume(self, subscribe, reason: str):
if external else
f"{format_subscribe(subscribe)} 检测到下载任务,已恢复暂停订阅"
)
notification_image_fn = self.get("notification_image_fn")
notify_fn(
title,
reason=self._pause_reason_label(reason),
Expand All @@ -541,6 +550,7 @@ def _notify_download_resume(self, subscribe, reason: str):
if external else
"48小时内不会因同一原因再次自动暂停"
),
image=notification_image_fn(subscribe) if notification_image_fn else None,
)

@staticmethod
Expand Down
10 changes: 9 additions & 1 deletion plugins.v2/subscribeassistantenhanced/postcheck/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def __init__(self, task_data_read: Callable, task_data_update: Callable,
subscribe_oper=None,
retention_days: int = 180,
notify_fn: Optional[Callable] = None,
rebuild_subscribe_fn: Optional[Callable] = None):
rebuild_subscribe_fn: Optional[Callable] = None,
get_subscribe_image_fn: Optional[Callable] = None):
"""注入完成快照存储、集数查询、订阅查询和真实订阅重建能力。"""
self._read = task_data_read
self._update = task_data_update
Expand All @@ -26,6 +27,7 @@ def __init__(self, task_data_read: Callable, task_data_update: Callable,
self._retention_seconds = retention_days * 86400
self._notify = notify_fn
self._rebuild_subscribe = rebuild_subscribe_fn
self._get_subscribe_image = get_subscribe_image_fn

def snapshot(self, subscribe, mediainfo, scope: Optional[SeasonScope]):
"""保存完成快照,同一季同一剧集组只保留最新记录。"""
Expand All @@ -43,6 +45,11 @@ def snapshot(self, subscribe, mediainfo, scope: Optional[SeasonScope]):
"completed_at": time.time(),
"subscribe_config": _extract_config(subscribe),
}
image = self._get_subscribe_image(subscribe) if self._get_subscribe_image else None
if not image and mediainfo:
image = mediainfo.get_message_image()
if image:
snap["subscribe_image"] = image

def updater(data: dict) -> dict:
snapshots = data.get("list", [])
Expand Down Expand Up @@ -141,6 +148,7 @@ def _rebuild(self, snap: dict, current_total: int) -> bool:
action_text = "已移除旧洗版订阅并重建订阅" if removed_full_best_version else "已自动重建订阅"
self._notify(
f"{name}{season_text} 检测到新增集数({old_total}→{current_total}),{action_text}",
image=snap.get("subscribe_image"),
)
return True

Expand Down
1 change: 1 addition & 0 deletions tests/v2/subscribeassistantenhanced/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def test_success(self):
assert send_event.call_args.args[1]["subscribe_id"] == 9
notify.assert_called_once()
assert notify.call_args.args[0] == "测试剧 S1 分集洗版集数已符合目标集数,已从分集洗版转为全集洗版订阅"
assert "user" not in notify.call_args.kwargs
assert "reason" not in notify.call_args.kwargs

def test_failure_keeps_original(self):
Expand Down
Loading