diff --git a/package.json b/package.json index 8334522..1161088 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "AutoSignIn": { "name": "站点自动签到", "description": "自动模拟登录站点、签到。", - "version": "1.3.1", + "version": "1.4", "icon": "signin.png", "author": "thsrite", "level": 2 @@ -18,7 +18,7 @@ "SiteStatistic": { "name": "站点数据统计", "description": "自动统计和展示站点数据。", - "version": "1.6", + "version": "1.7", "icon": "statistic.png", "author": "lightolly", "level": 2 @@ -26,7 +26,7 @@ "SiteRefresh": { "name": "站点自动更新", "description": "使用浏览器模拟登录站点获取Cookie和UA。", - "version": "1.1", + "version": "1.2", "icon": "Chrome_A.png", "author": "thsrite", "level": 2 @@ -34,7 +34,7 @@ "DoubanSync": { "name": "豆瓣想看", "description": "同步豆瓣想看数据,自动添加订阅。", - "version": "1.2", + "version": "1.3", "icon": "douban.png", "author": "jxxghp", "level": 2 @@ -42,7 +42,7 @@ "DirMonitor": { "name": "目录监控", "description": "监控目录文件发生变化时实时整理到媒体库。", - "version": "1.6", + "version": "1.7", "icon": "directory.png", "author": "jxxghp", "level": 1 @@ -58,7 +58,7 @@ "DoubanRank": { "name": "豆瓣榜单订阅", "description": "监控豆瓣热门榜单,自动添加订阅。", - "version": "1.4", + "version": "1.5", "icon": "movie.jpg", "author": "jxxghp", "level": 2 @@ -66,7 +66,7 @@ "LibraryScraper": { "name": "媒体库刮削", "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", - "version": "1.2", + "version": "1.3", "icon": "scraper.png", "author": "jxxghp", "level": 1 @@ -74,7 +74,7 @@ "TorrentRemover": { "name": "自动删种", "description": "自动删除下载器中的下载任务。", - "version": "1.1", + "version": "1.2", "icon": "delete.jpg", "author": "jxxghp", "level": 2 @@ -82,7 +82,7 @@ "MediaSyncDel": { "name": "媒体文件同步删除", "description": "同步删除历史记录、源文件和下载任务。", - "version": "1.2", + "version": "1.3", "icon": "mediasyncdel.png", "author": "thsrite", "level": 1 @@ -98,7 +98,7 @@ "SpeedLimiter": { "name": "播放限速", "description": "外网播放媒体库视频时,自动对下载器进行限速。", - "version": "1.0", + "version": "1.1", "icon": "Librespeed_A.png", "author": "Shurelol", "level": 1 @@ -106,7 +106,7 @@ "CloudflareSpeedTest": { "name": "Cloudflare IP优选", "description": "🌩 测试 Cloudflare CDN 延迟和速度,自动优选IP。", - "version": "1.0", + "version": "1.1", "icon": "cloudflare.jpg", "author": "thsrite", "level": 1 @@ -114,7 +114,7 @@ "BestFilmVersion": { "name": "收藏洗版", "description": "Jellyfin/Emby/Plex点击收藏电影后,自动订阅洗版。", - "version": "2.0", + "version": "2.1", "icon": "like.jpg", "author": "wlj", "level": 2 @@ -130,7 +130,7 @@ "MediaServerRefresh": { "name": "媒体库服务器刷新", "description": "入库后自动刷新Emby/Jellyfin/Plex服务器海报墙。", - "version": "1.1", + "version": "1.2", "icon": "refresh2.png", "author": "jxxghp", "level": 1 @@ -170,7 +170,7 @@ "AutoBackup": { "name": "自动备份", "description": "自动备份数据和配置文件。", - "version": "1.0", + "version": "1.1", "icon": "Time_machine_B.png", "author": "thsrite", "level": 1 @@ -178,7 +178,7 @@ "IYUUAutoSeed": { "name": "IYUU自动辅种", "description": "基于IYUU官方Api实现自动辅种。", - "version": "1.2", + "version": "1.3", "icon": "IYUU.png", "author": "jxxghp", "level": 2 @@ -186,7 +186,7 @@ "TorrentTransfer": { "name": "自动转移做种", "description": "定期转移下载器中的做种任务到另一个下载器。", - "version": "1.1", + "version": "1.2", "icon": "seed.png", "author": "jxxghp", "level": 2 @@ -194,7 +194,7 @@ "RssSubscribe": { "name": "自定义订阅", "description": "定时刷新RSS报文,识别内容后添加订阅或直接下载。", - "version": "1.0", + "version": "1.1", "icon": "rss.png", "author": "jxxghp", "level": 2 @@ -202,7 +202,7 @@ "SyncDownloadFiles": { "name": "下载器文件同步", "description": "同步下载器的文件信息到数据库,删除文件时联动删除下载任务。", - "version": "1.0", + "version": "1.1", "icon": "Youtube-dl_A.png", "author": "thsrite", "level": 1 @@ -210,7 +210,7 @@ "BrushFlow": { "name": "站点刷流", "description": "自动托管刷流,将会提高对应站点的访问频率。", - "version": "1.1", + "version": "1.2", "icon": "brush.jpg", "author": "jxxghp", "level": 2 @@ -218,7 +218,7 @@ "DownloadingMsg": { "name": "下载进度推送", "description": "定时推送正在下载进度。", - "version": "1.0", + "version": "1.1", "icon": "downloadmsg.png", "author": "thsrite", "level": 2 @@ -226,7 +226,7 @@ "AutoClean": { "name": "定时清理媒体库", "description": "定时清理用户下载的种子、源文件、媒体库文件。", - "version": "1.0", + "version": "1.1", "icon": "clean.png", "author": "thsrite", "level": 2 @@ -234,7 +234,7 @@ "InvitesSignin": { "name": "药丸签到", "description": "药丸论坛签到。", - "version": "1.2", + "version": "1.3", "icon": "invites.png", "author": "thsrite", "level": 2 @@ -242,7 +242,7 @@ "PersonMeta": { "name": "演职人员刮削", "description": "刮削演职人员图片以及中文名称。", - "version": "1.1", + "version": "1.2", "icon": "actor.png", "author": "jxxghp", "level": 1 @@ -250,7 +250,7 @@ "MoviePilotUpdateNotify": { "name": "MoviePilot更新推送", "description": "MoviePilot推送release更新通知、自动重启。", - "version": "1.0", + "version": "1.1", "icon": "Moviepilot_A.png", "author": "thsrite", "level": 1 @@ -306,7 +306,7 @@ "EpisodeGroupMeta": { "name": "TMDB剧集组刮削", "description": "从TMDB剧集组刮削季集的实际顺序", - "version": "1.0", + "version": "1.1", "icon": "Element_A.png", "author": "叮叮当", "level": 1 @@ -338,7 +338,7 @@ "DownloadSiteTag": { "name": "下载任务分类与标签", "description": "自动给下载任务分类与打站点标签、剧集名称标签", - "version": "1.7", + "version": "1.8", "icon": "Youtube-dl_B.png", "author": "叮叮当", "level": 1 @@ -346,7 +346,7 @@ "RemoveLink": { "name": "清理硬链接", "description": "监控目录内文件被删除时,同步删除监控目录内所有和它硬链接的文件", - "version": "1.1", + "version": "1.2", "icon": "Ombi_A.png", "author": "DzAvril", "level": 1 @@ -354,7 +354,7 @@ "LinkMonitor": { "name": "实时硬链接", "description": "监控目录文件变化,实时硬链接。", - "version": "1.2", + "version": "1.3", "icon": "Linkace_C.png", "author": "jxxghp", "level": 1 diff --git a/plugins/autobackup/__init__.py b/plugins/autobackup/__init__.py index 2202e48..99c5923 100644 --- a/plugins/autobackup/__init__.py +++ b/plugins/autobackup/__init__.py @@ -25,7 +25,7 @@ class AutoBackup(_PluginBase): # 插件图标 plugin_icon = "Time_machine_B.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -60,32 +60,21 @@ class AutoBackup(_PluginBase): self._onlyonce = config.get("onlyonce") # 加载模块 - if self._enabled: - # 定时服务 + if self._onlyonce: self._scheduler = BackgroundScheduler(timezone=settings.TZ) - - if self._cron: - try: - self._scheduler.add_job(func=self.__backup, - trigger=CronTrigger.from_crontab(self._cron), - name="自动备份") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - - if self._onlyonce: - logger.info(f"自动备份服务启动,立即运行一次") - self._scheduler.add_job(func=self.__backup, trigger='date', - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), - name="自动备份") - # 关闭一次性开关 - self._onlyonce = False - self.update_config({ - "onlyonce": False, - "cron": self._cron, - "enabled": self._enabled, - "cnt": self._cnt, - "notify": self._notify, - }) + logger.info(f"自动备份服务启动,立即运行一次") + self._scheduler.add_job(func=self.__backup, trigger='date', + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), + name="自动备份") + # 关闭一次性开关 + self._onlyonce = False + self.update_config({ + "onlyonce": False, + "cron": self._cron, + "enabled": self._enabled, + "cnt": self._cnt, + "notify": self._notify, + }) # 启动任务 if self._scheduler.get_jobs(): @@ -186,6 +175,26 @@ class AutoBackup(_PluginBase): "description": "MoviePilot备份", }] + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "AutoBackup", + "name": "自动备份定时服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.__backup, + "kwargs": {} + }] + def backup(self) -> schemas.Response: """ API调用备份 diff --git a/plugins/autoclean/__init__.py b/plugins/autoclean/__init__.py index 12c898b..d7ba6c5 100644 --- a/plugins/autoclean/__init__.py +++ b/plugins/autoclean/__init__.py @@ -27,7 +27,7 @@ class AutoClean(_PluginBase): # 插件图标 plugin_icon = "clean.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -72,18 +72,10 @@ class AutoClean(_PluginBase): if self._enabled: self._downloadhis = DownloadHistoryOper() self._transferhis = TransferHistoryOper() - # 定时服务 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - - if self._cron: - try: - self._scheduler.add_job(func=self.__clean, - trigger=CronTrigger.from_crontab(self._cron), - name="定时清理媒体库") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") if self._onlyonce: + # 定时服务 + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info(f"定时清理媒体库服务启动,立即运行一次") self._scheduler.add_job(func=self.__clean, trigger='date', run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), @@ -100,10 +92,10 @@ class AutoClean(_PluginBase): "notify": self._notify, }) - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() def __get_clean_date(self, deltatime: str = None): # 清理日期 @@ -263,6 +255,28 @@ class AutoClean(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [ + { + "id": "AutoClean", + "name": "清理媒体库定时服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.__clean, + "kwargs": {} + } + ] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/autosignin/__init__.py b/plugins/autosignin/__init__.py index c5a0615..96e2b07 100644 --- a/plugins/autosignin/__init__.py +++ b/plugins/autosignin/__init__.py @@ -36,7 +36,7 @@ class AutoSignIn(_PluginBase): # 插件图标 plugin_icon = "signin.png" # 插件版本 - plugin_version = "1.3.1" + plugin_version = "1.4" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -107,11 +107,10 @@ class AutoSignIn(_PluginBase): self._site_schema = ModuleHelper.load('app.plugins.autosignin.sites', filter_func=lambda _, obj: hasattr(obj, 'match')) - # 定时服务 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - # 立即运行一次 if self._onlyonce: + # 定时服务 + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info("站点自动签到服务启动,立即运行一次") self._scheduler.add_job(func=self.sign_in, trigger='date', run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), @@ -122,75 +121,10 @@ class AutoSignIn(_PluginBase): # 保存配置 self.__update_config() - # 周期运行 - if self._enabled: - if self._cron: - try: - if str(self._cron).strip().count(" ") == 4: - self._scheduler.add_job(func=self.sign_in, - trigger=CronTrigger.from_crontab(self._cron), - name="站点自动签到") - logger.info(f"站点自动签到服务启动,执行周期 {self._cron}") - else: - # 2.3/9-23 - crons = str(self._cron).strip().split("/") - if len(crons) == 2: - # 2.3 - cron = crons[0] - # 9-23 - times = crons[1].split("-") - if len(times) == 2: - # 9 - self._start_time = int(times[0]) - # 23 - self._end_time = int(times[1]) - if self._start_time and self._end_time: - self._scheduler.add_job(func=self.sign_in, - trigger="interval", - hours=float(str(cron).strip()), - name="站点自动签到") - logger.info( - f"站点自动签到服务启动,执行周期 {self._start_time}点-{self._end_time}点 每{cron}小时执行一次") - else: - logger.error("站点自动签到服务启动失败,周期格式错误") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误") - self._cron = "" - self._enabled = False - self.__update_config() - else: - # 默认0-24 按照周期运行 - self._start_time = 0 - self._end_time = 24 - self._scheduler.add_job(func=self.sign_in, - trigger="interval", - hours=float(str(self._cron).strip()), - name="站点自动签到") - logger.info( - f"站点自动签到服务启动,执行周期 {self._start_time}点-{self._end_time}点 每{self._cron}小时执行一次") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - self._cron = "" - self._enabled = False - self.__update_config() - else: - # 随机时间 - triggers = TimerUtils.random_scheduler(num_executions=2, - begin_hour=9, - end_hour=23, - max_interval=6 * 60, - min_interval=2 * 60) - for trigger in triggers: - self._scheduler.add_job(self.sign_in, "cron", - hour=trigger.hour, minute=trigger.minute, - name="站点自动签到") - - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() def get_state(self) -> bool: return self._enabled @@ -246,6 +180,87 @@ class AutoSignIn(_PluginBase): "description": "使用站点域名签到站点", }] + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + try: + if str(self._cron).strip().count(" ") == 4: + return [{ + "id": "AutoSignIn", + "name": "站点自动签到服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.sign_in, + "kwargs": {} + }] + else: + # 2.3/9-23 + crons = str(self._cron).strip().split("/") + if len(crons) == 2: + # 2.3 + cron = crons[0] + # 9-23 + times = crons[1].split("-") + if len(times) == 2: + # 9 + self._start_time = int(times[0]) + # 23 + self._end_time = int(times[1]) + if self._start_time and self._end_time: + return [{ + "id": "AutoSignIn", + "name": "站点自动签到服务", + "trigger": "interval", + "func": self.sign_in, + "kwargs": { + "hours": float(str(cron).strip()), + } + }] + else: + logger.error("站点自动签到服务启动失败,周期格式错误") + else: + # 默认0-24 按照周期运行 + return [{ + "id": "AutoSignIn", + "name": "站点自动签到服务", + "trigger": "interval", + "func": self.sign_in, + "kwargs": { + "hours": float(str(self._cron).strip()), + } + }] + except Exception as err: + logger.error(f"定时任务配置错误:{str(err)}") + elif self._enabled: + # 随机时间 + triggers = TimerUtils.random_scheduler(num_executions=2, + begin_hour=9, + end_hour=23, + max_interval=6 * 60, + min_interval=2 * 60) + ret_jobs = [] + for trigger in triggers: + ret_jobs.append({ + "id": f"AutoSignIn|{trigger.hour}:{trigger.minute}", + "name": "站点自动签到服务", + "trigger": "cron", + "func": self.sign_in, + "kwargs": { + "hour": trigger.hour, + "minute": trigger.minute + } + }) + return ret_jobs + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/bestfilmversion/__init__.py b/plugins/bestfilmversion/__init__.py index cbcf101..b015db3 100644 --- a/plugins/bestfilmversion/__init__.py +++ b/plugins/bestfilmversion/__init__.py @@ -34,7 +34,7 @@ class BestFilmVersion(_PluginBase): # 插件图标 plugin_icon = "like.jpg" # 插件版本 - plugin_version = "2.0" + plugin_version = "2.1" # 插件作者 plugin_author = "wlj" # 作者主页 @@ -73,33 +73,19 @@ class BestFilmVersion(_PluginBase): self._webhook_enabled = config.get("webhook_enabled") self._only_once = config.get("only_once") - if self._enabled: + if 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._scheduler = BackgroundScheduler(timezone=settings.TZ) - if not self._webhook_enabled: - if self._cron: - try: - self._scheduler.add_job(func=self.sync, - trigger=CronTrigger.from_crontab(self._cron), - name="收藏洗版") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - else: - self._scheduler.add_job(self.sync, "interval", minutes=30, name="收藏洗版") - - if 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._scheduler.add_job(self.sync, 'date', - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), - name="立即运行收藏洗版") + self._scheduler.add_job(self.sync, 'date', + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), + name="立即运行收藏洗版") # 启动任务 if self._scheduler.get_jobs(): self._scheduler.print_jobs() @@ -124,6 +110,39 @@ class BestFilmVersion(_PluginBase): """ pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and not self._webhook_enabled: + if self._cron: + return [{ + "id": "BestFilmVersion", + "name": "收藏洗版定时服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.sync, + "kwargs": {} + }] + return [ + { + "id": "BestFilmVersion", + "name": "收藏洗版定时服务", + "trigger": "interval", + "func": self.sync, + "kwargs": { + "minutes": 30 + } + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index 44a0c51..b49d1bc 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -31,7 +31,7 @@ class BrushFlow(_PluginBase): # 插件图标 plugin_icon = "brush.jpg" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -50,8 +50,9 @@ class BrushFlow(_PluginBase): sites = None qb = None tr = None - # 添加种子定时 + # 添加种子定时 分钟 _cron = 10 + _task_enable = False # 检查种子定时 _check_interval = 5 # 退出事件 @@ -137,6 +138,7 @@ class BrushFlow(_PluginBase): self.stop_service() # 启动定时任务 & 立即运行一次 + self._task_enable = False if self.get_state() or self._onlyonce: self.qb = Qbittorrent() self.tr = Transmission() @@ -231,15 +233,11 @@ class BrushFlow(_PluginBase): return # 启动任务 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - logger.info(f"站点刷流服务启动,周期:{self._cron}分钟") - try: - self._scheduler.add_job(self.brush, 'interval', minutes=self._cron) - except Exception as e: - logger.error(f"站点刷流服务启动失败:{str(e)}") - self.systemmessage.put(f"站点刷流服务启动失败:{str(e)}") - return + self._task_enable = True + + # 仅一次 if self._onlyonce: + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info(f"站点刷流服务启动,立即运行一次") self._scheduler.add_job(self.brush, 'date', run_date=datetime.now( @@ -249,14 +247,14 @@ class BrushFlow(_PluginBase): # 关闭一次性开关 self._onlyonce = False self.__update_config() - if self._scheduler.get_jobs(): - # 增加检查任务 - self._scheduler.add_job(self.check, 'interval', - minutes=self._check_interval, - name="站点刷流检查服务") - # 启动服务 - self._scheduler.print_jobs() - self._scheduler.start() + if self._scheduler.get_jobs(): + # 增加检查任务 + self._scheduler.add_job(self.check, 'interval', + minutes=self._check_interval, + name="站点刷流检查服务") + # 启动服务 + self._scheduler.print_jobs() + self._scheduler.start() def get_state(self) -> bool: return True if self._enabled and self._brushsites and self._downloader else False @@ -268,6 +266,27 @@ class BrushFlow(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._task_enable: + return [{ + "id": "BrushFlow", + "name": "站点刷流服务", + "trigger": "interval", + "func": self.brush, + "kwargs": {"minutes": self._cron} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 @@ -756,26 +775,26 @@ class BrushFlow(_PluginBase): ] }, { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - }, - 'content': [ - { - 'component': 'VAlert', - 'props': { - 'type': 'info', - 'variant': 'tonal', - 'text': '注意:排除H&R并不保证能完全适配所有站点(部分站点在列表页不显示H&R标志,但实际上是有H&R的),请注意核对使用!' - } - } - ] - } - ] - } + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': '注意:排除H&R并不保证能完全适配所有站点(部分站点在列表页不显示H&R标志,但实际上是有H&R的),请注意核对使用!' + } + } + ] + } + ] + } ] } ], { @@ -1921,7 +1940,7 @@ class BrushFlow(_PluginBase): return len(torrents) or 0 @staticmethod - def __get_pubminutes(pubdate: str) -> int: + def __get_pubminutes(pubdate: str) -> float: """ 将字符串转换为时间,并计算与当前时间差)(分钟) """ diff --git a/plugins/cloudflarespeedtest/__init__.py b/plugins/cloudflarespeedtest/__init__.py index e1417cd..fc82e18 100644 --- a/plugins/cloudflarespeedtest/__init__.py +++ b/plugins/cloudflarespeedtest/__init__.py @@ -32,7 +32,7 @@ class CloudflareSpeedTest(_PluginBase): # 插件图标 plugin_icon = "cloudflare.jpg" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -81,34 +81,25 @@ class CloudflareSpeedTest(_PluginBase): self._notify = config.get("notify") self._check = config.get("check") - if self.get_state() or self._onlyonce: - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - + if self.get_state() and self._onlyonce: try: - if self.get_state() and self._cron: - logger.info(f"Cloudflare CDN优选服务启动,周期:{self._cron}") - self._scheduler.add_job(func=self.__cloudflareSpeedTest, - trigger=CronTrigger.from_crontab(self._cron), - name="Cloudflare优选") - - if self._onlyonce: - logger.info(f"Cloudflare CDN优选服务启动,立即运行一次") - self._scheduler.add_job(func=self.__cloudflareSpeedTest, trigger='date', - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), - name="Cloudflare优选") - # 关闭一次性开关 - self._onlyonce = False - self.__update_config() + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + logger.info(f"Cloudflare CDN优选服务启动,立即运行一次") + self._scheduler.add_job(func=self.__cloudflareSpeedTest, trigger='date', + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), + name="Cloudflare优选") + # 关闭一次性开关 + self._onlyonce = False + self.__update_config() + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() except Exception as err: logger.error(f"Cloudflare CDN优选服务出错:{str(err)}") self.systemmessage.put(f"Cloudflare CDN优选服务出错:{str(err)}") return - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() - @eventmanager.register(EventType.PluginAction) def __cloudflareSpeedTest(self, event: Event = None): """ @@ -324,9 +315,11 @@ class CloudflareSpeedTest(_PluginBase): install_flag = True # 重装后数据库有版本数据,但是本地没有则重装 - if not install_flag and release_version == self._version and not Path( - f'{self._cf_path}/{self._binary_name}').exists() and not Path( - f'{self._cf_path}/CloudflareST.exe').exists(): + if not install_flag \ + and release_version == self._version \ + and not Path( + f'{self._cf_path}/{self._binary_name}').exists() \ + and not Path(f'{self._cf_path}/CloudflareST.exe').exists(): logger.warn(f"未检测到CloudflareSpeedTest本地版本,重新安装") install_flag = True @@ -503,6 +496,29 @@ class CloudflareSpeedTest(_PluginBase): "description": "Cloudflare IP优选", }] + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self.get_state(): + return [ + { + "id": "CloudflareSpeedTest", + "name": "Cloudflare IP优选服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.__cloudflareSpeedTest, + "kwargs": {} + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/dirmonitor/__init__.py b/plugins/dirmonitor/__init__.py index 2a4bba4..c1449bb 100644 --- a/plugins/dirmonitor/__init__.py +++ b/plugins/dirmonitor/__init__.py @@ -59,7 +59,7 @@ class DirMonitor(_PluginBase): # 插件图标 plugin_icon = "directory.png" # 插件版本 - plugin_version = "1.6" + plugin_version = "1.7" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -216,17 +216,6 @@ class DirMonitor(_PluginBase): # 保存配置 self.__update_config() - # 全量同步定时 - if self._enabled and self._cron: - try: - self._scheduler.add_job(func=self.sync_all, - trigger=CronTrigger.from_crontab(self._cron), - name="目录监控全量同步") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - # 启动定时服务 if self._scheduler.get_jobs(): self._scheduler.print_jobs() @@ -643,6 +632,27 @@ class DirMonitor(_PluginBase): "description": "目录监控同步", }] + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "DirMonitor", + "name": "目录监控全量同步服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.sync_all, + "kwargs": {} + }] + return [] + def sync(self) -> schemas.Response: """ API调用目录同步 diff --git a/plugins/doubanrank/__init__.py b/plugins/doubanrank/__init__.py index 28ebd52..401d257 100644 --- a/plugins/doubanrank/__init__.py +++ b/plugins/doubanrank/__init__.py @@ -27,7 +27,7 @@ class DoubanRank(_PluginBase): # 插件图标 plugin_icon = "movie.jpg" # 插件版本 - plugin_version = "1.4" + plugin_version = "1.5" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -90,28 +90,19 @@ class DoubanRank(_PluginBase): # 启动服务 if self._enabled or self._onlyonce: - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - if self._cron: - logger.info(f"豆瓣榜单订阅服务启动,周期:{self._cron}") - try: - self._scheduler.add_job(func=self.__refresh_rss, - trigger=CronTrigger.from_crontab(self._cron), - name="豆瓣榜单订阅") - except Exception as e: - logger.error(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}") - self.systemmessage.put(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}") - else: - self._scheduler.add_job(func=self.__refresh_rss, trigger=CronTrigger.from_crontab("0 8 * * *"), - name="豆瓣榜单订阅") - logger.info("豆瓣榜单订阅服务启动,周期:每天 08:00") - if self._onlyonce: + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info("豆瓣榜单订阅服务启动,立即运行一次") self._scheduler.add_job(func=self.__refresh_rss, trigger='date', run_date=datetime.datetime.now( tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) ) + if self._scheduler.get_jobs(): + # 启动服务 + self._scheduler.print_jobs() + self._scheduler.start() + if self._onlyonce or self._clear: # 关闭一次性开关 self._onlyonce = False @@ -122,11 +113,6 @@ class DoubanRank(_PluginBase): # 保存配置 self.__update_config() - if self._scheduler.get_jobs(): - # 启动服务 - self._scheduler.print_jobs() - self._scheduler.start() - def get_state(self) -> bool: return self._enabled @@ -137,6 +123,39 @@ class DoubanRank(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [ + { + "id": "DoubanRank", + "name": "豆瓣榜单订阅服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.__refresh_rss, + "kwargs": {} + } + ] + elif self._enabled: + return [ + { + "id": "DoubanRank", + "name": "豆瓣榜单订阅服务", + "trigger": CronTrigger.from_crontab("0 8 * * *"), + "func": self.__refresh_rss, + "kwargs": {} + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: return [ { diff --git a/plugins/doubansync/__init__.py b/plugins/doubansync/__init__.py index bd7eff3..72cde40 100644 --- a/plugins/doubansync/__init__.py +++ b/plugins/doubansync/__init__.py @@ -31,7 +31,7 @@ class DoubanSync(_PluginBase): # 插件图标 plugin_icon = "douban.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.3" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -82,27 +82,19 @@ class DoubanSync(_PluginBase): self._clear = config.get("clear") if self._enabled or self._onlyonce: - - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - if self._cron: - try: - self._scheduler.add_job(func=self.sync, - trigger=CronTrigger.from_crontab(self._cron), - name="豆瓣想看") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - else: - self._scheduler.add_job(self.sync, "interval", minutes=30, name="豆瓣想看") - if self._onlyonce: + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info(f"豆瓣想看服务启动,立即运行一次") self._scheduler.add_job(func=self.sync, trigger='date', run_date=datetime.datetime.now( tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) ) + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() + if self._onlyonce or self._clear: # 关闭一次性开关 self._onlyonce = False @@ -113,11 +105,6 @@ class DoubanSync(_PluginBase): # 保存配置 self.__update_config() - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() - def get_state(self) -> bool: return self._enabled @@ -149,6 +136,39 @@ class DoubanSync(_PluginBase): """ pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [ + { + "id": "DoubanSync", + "name": "豆瓣想看同步服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.sync, + "kwargs": {} + } + ] + elif self._enabled: + return [ + { + "id": "DoubanSync", + "name": "豆瓣想看同步服务", + "trigger": "interval", + "func": self.sync, + "kwargs": {"minutes": 30} + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/downloadingmsg/__init__.py b/plugins/downloadingmsg/__init__.py index c9d4bff..130d787 100644 --- a/plugins/downloadingmsg/__init__.py +++ b/plugins/downloadingmsg/__init__.py @@ -1,12 +1,11 @@ -from apscheduler.schedulers.background import BackgroundScheduler +from typing import Any, List, Dict, Tuple, Optional, Union from app.chain.download import DownloadChain from app.chain.media import MediaChain -from app.core.config import settings +from app.core.metainfo import MetaInfo from app.db.downloadhistory_oper import DownloadHistoryOper -from app.plugins import _PluginBase -from typing import Any, List, Dict, Tuple, Optional, Union from app.log import logger +from app.plugins import _PluginBase from app.schemas import NotificationType, TransferTorrent, DownloadingTorrent from app.schemas.types import TorrentStatus, MessageChannel from app.utils.string import StringUtils @@ -20,7 +19,7 @@ class DownloadingMsg(_PluginBase): # 插件图标 plugin_icon = "downloadmsg.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -40,10 +39,8 @@ class DownloadingMsg(_PluginBase): _adminuser = None _downloadhis = None - # 定时器 - _scheduler: Optional[BackgroundScheduler] = None - def init_plugin(self, config: dict = None): + self._downloadhis = DownloadHistoryOper() # 停止现有任务 self.stop_service() @@ -53,26 +50,6 @@ class DownloadingMsg(_PluginBase): self._type = config.get("type") or 'admin' self._adminuser = config.get("adminuser") - # 加载模块 - if self._enabled: - self._downloadhis = DownloadHistoryOper() - # 定时服务 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - - if self._seconds: - try: - self._scheduler.add_job(func=self.__downloading, - trigger='interval', - seconds=int(self._seconds), - name="下载进度推送") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() - def __downloading(self): """ 定时推送正在下载进度 @@ -154,10 +131,10 @@ class DownloadingMsg(_PluginBase): channel_value = downloadhis.channel else: try: - context = MediaChain().recognize_by_title(title=torrent.title) - if not context or not context.media_info: + meta = MetaInfo(torrent.title) + media_info = MediaChain().recognize_media(meta) + if not media_info: continue - media_info = context.media_info year = media_info.year name = media_info.title if media_info.number_of_seasons: @@ -207,6 +184,31 @@ class DownloadingMsg(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._seconds: + return [ + { + "id": "downloading", + "name": "下载进度推送服务", + "trigger": "interval", + "func": self.__downloading, + "kwargs": { + "seconds": int(self._seconds) + } + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 @@ -309,14 +311,4 @@ class DownloadingMsg(_PluginBase): pass def stop_service(self): - """ - 退出插件 - """ - try: - if self._scheduler: - self._scheduler.remove_all_jobs() - if self._scheduler.running: - self._scheduler.shutdown() - self._scheduler = None - except Exception as e: - logger.error("退出插件失败:%s" % str(e)) + pass diff --git a/plugins/downloadsitetag/__init__.py b/plugins/downloadsitetag/__init__.py index 9b42caa..6504503 100644 --- a/plugins/downloadsitetag/__init__.py +++ b/plugins/downloadsitetag/__init__.py @@ -1,7 +1,7 @@ import datetime import pytz import threading -from typing import List, Tuple, Dict, Any +from typing import List, Tuple, Dict, Any, Optional from app.core.context import Context from app.core.event import eventmanager, Event @@ -19,6 +19,7 @@ from apscheduler.triggers.cron import CronTrigger from app.helper.sites import SitesHelper from app.utils.string import StringUtils + class DownloadSiteTag(_PluginBase): # 插件名称 plugin_name = "下载任务分类与标签" @@ -27,7 +28,7 @@ class DownloadSiteTag(_PluginBase): # 插件图标 plugin_icon = "Youtube-dl_B.png" # 插件版本 - plugin_version = "1.7" + plugin_version = "1.8" # 插件作者 plugin_author = "叮叮当" # 作者主页 @@ -40,7 +41,7 @@ class DownloadSiteTag(_PluginBase): auth_level = 1 # 日志前缀 LOG_TAG = "[DownloadSiteTag] " - + # 退出事件 _event = threading.Event() # 私有属性 @@ -86,57 +87,32 @@ class DownloadSiteTag(_PluginBase): if not ("interval_cron" in config): # 新版本v1.6更新插件配置默认配置 config["interval"] = self._interval - config["interval_cron"] = self._interval_cron + config["interval_cron"] = self._interval_cron config["interval_time"] = self._interval_time config["interval_unit"] = self._interval_unit self.update_config(config) logger.warn(f"{self.LOG_TAG}新版本v{self.plugin_version} 配置修正 ...") - + # 停止现有任务 self.stop_service() - if self._enabled or self._onlyonce: + if self._onlyonce: # 创建定时任务控制器 self._scheduler = BackgroundScheduler(timezone=settings.TZ) - - if self._onlyonce: # 执行一次, 关闭onlyonce self._onlyonce = False config.update({"onlyonce": self._onlyonce}) self.update_config(config) # 添加 补全下载历史的标签与分类 任务 - self._scheduler.add_job(func= self._complemented_history, trigger='date', - run_date=datetime.datetime.now( - tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) - ) - if self._enabled: - if self._interval == "计划任务" or self._interval == "固定间隔": - args = {} - if self._interval == "固定间隔": - args["trigger"] = "interval" - if self._interval_unit == "小时": - args["hours"] = self._interval_time - else: - args["minutes"] = self._interval_time - if args["minutes"] < 5: - args["minutes"] = 5 - logger.info(f"{self.LOG_TAG}启动定时服务: 最小不少于5分钟, 防止执行间隔太短任务冲突") - else: - args["trigger"] = CronTrigger.from_crontab(self._interval_cron) - try: - self._scheduler.add_job(func=lambda: self._complemented_history(interval=True), - **args, - name="补全下载历史的标签与分类") - logger.info( - f"{self.LOG_TAG}添加定时服务: 补全下载历史的标签与分类" + (f"(每){args.get('hours') or args.get('minutes')}{self._interval_unit}执行一次" if args["trigger"] == "interval" else f",计划任务: {self._interval_cron}")) - except Exception as e: - logger.error( - f"{self.LOG_TAG}添加定时服务发生了错误: {str(e)}") + self._scheduler.add_job(func=self._complemented_history, trigger='date', + run_date=datetime.datetime.now( + tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) + ) - if self._scheduler and self._scheduler.get_jobs(): - # 启动服务 - self._scheduler.print_jobs() - self._scheduler.start() + if self._scheduler and self._scheduler.get_jobs(): + # 启动服务 + self._scheduler.print_jobs() + self._scheduler.start() def get_state(self) -> bool: return self._enabled @@ -145,21 +121,68 @@ class DownloadSiteTag(_PluginBase): def get_command() -> List[Dict[str, Any]]: pass - def get_api(self) -> List[Dict[str, Any]]: pass - def str_to_number(self, s: str, i: int) -> int: + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled: + if self._interval == "计划任务" or self._interval == "固定间隔": + if self._interval == "固定间隔": + if self._interval_unit == "小时": + return [{ + "id": "DownloadSiteTag", + "name": "补全下载历史的标签与分类", + "trigger": "interval", + "func": self._complemented_history, + "kwargs": { + "hours": self._interval_time + } + }] + else: + if self._interval_time < 5: + self._interval_time = 5 + logger.info(f"{self.LOG_TAG}启动定时服务: 最小不少于5分钟, 防止执行间隔太短任务冲突") + return [{ + "id": "DownloadSiteTag", + "name": "补全下载历史的标签与分类", + "trigger": "interval", + "func": self._complemented_history, + "kwargs": { + "minutes": self._interval_time + } + }] + else: + return [{ + "id": "DownloadSiteTag", + "name": "补全下载历史的标签与分类", + "trigger": CronTrigger.from_crontab(self._interval_cron), + "func": self._complemented_history, + "kwargs": {} + }] + return [] + + @staticmethod + def str_to_number(s: str, i: int) -> int: try: return int(s) - except: + except ValueError: return i - def _complemented_history(self, interval: bool = False): + def _complemented_history(self): """ 补全下载历史的标签与分类 """ - logger.info(f"{self.LOG_TAG}开始执行{'(定时任务)' if interval else ''}: 补全下载历史的标签与分类 ...") + logger.info(f"{self.LOG_TAG}开始执行 ...") # 记录处理的种子, 供辅种(无下载历史)使用 dispose_history = {} # 所有站点索引 @@ -185,7 +208,8 @@ class DownloadSiteTag(_PluginBase): for torrent in torrents: try: if self._event.is_set(): - logger.info(f"{self.LOG_TAG}停止服务{'(定时任务)' if interval else ''}: 补全下载历史的标签与分类") + logger.info( + f"{self.LOG_TAG}停止服务") return # 获取已处理种子的key (size, name) _key = self._torrent_key(torrent=torrent, dl_type=DOWNLOADER) @@ -205,11 +229,7 @@ class DownloadSiteTag(_PluginBase): # 因为辅种站点必定不同, 所以需要更新站点名字 history.torrent_site history.torrent_site = None else: - history = DownloadHistory( - torrent_site=None, - title=None, - type=None, - tmdbid=None) + history = DownloadHistory() else: # 加入历史记录 if _key: @@ -243,13 +263,14 @@ class DownloadSiteTag(_PluginBase): # 如果是电视剧 需要区分是否动漫 genre_ids = None # 因允许tmdbid为空时运行到此, 因此需要判断tmdbid不为空 - if history.tmdbid and (history.type == MediaType.TV or history.type == MediaType.TV.value): + history_type = MediaType(history.type) if history.type else None + if history.tmdbid and history_type == MediaType.TV: # tmdb_id获取tmdb信息 - tmdb_info = self.tmdb_helper.get_info(mtype=history.type, tmdbid=history.tmdbid) + tmdb_info = self.tmdb_helper.get_info(mtype=history_type, tmdbid=history.tmdbid) if tmdb_info: genre_ids = tmdb_info.get("genre_ids") _cat = self._genre_ids_get_cat(history.type, genre_ids) - + # 去除种子已经存在的标签 if _tags and torrent_tags: _tags = list(set(_tags) - set(torrent_tags)) @@ -260,15 +281,15 @@ class DownloadSiteTag(_PluginBase): if not _cat and not _tags: continue # 执行通用方法, 设置种子标签与分类 - self._set_torrent_info(DOWNLOADER=DOWNLOADER, _hash=_hash, _torrent=torrent, _tags=_tags, _cat=_cat, _original_tags=torrent_tags) + self._set_torrent_info(DOWNLOADER=DOWNLOADER, _hash=_hash, _torrent=torrent, _tags=_tags, _cat=_cat, + _original_tags=torrent_tags) except Exception as e: logger.error( f"{self.LOG_TAG}分析种子信息时发生了错误: {str(e)}") + logger.info(f"{self.LOG_TAG}执行完成") - logger.info(f"{self.LOG_TAG}执行完成{'(定时任务)' if interval else ''}: 补全下载历史的标签与分类 ...") - - def _genre_ids_get_cat(self, mtype, genre_ids = None): + def _genre_ids_get_cat(self, mtype, genre_ids=None): """ 根据genre_ids判断是否<动漫>分类 """ @@ -298,12 +319,11 @@ class DownloadSiteTag(_PluginBase): else: return None - def _torrent_key(self, torrent: Any, dl_type: str): + @staticmethod + def _torrent_key(torrent: Any, dl_type: str) -> Optional[Tuple[int, str]]: """ 按种子大小和时间返回key """ - size = None - name = None if dl_type == "qbittorrent": size = torrent.get('size') name = torrent.get('name') @@ -313,9 +333,10 @@ class DownloadSiteTag(_PluginBase): if not size or not name: return None else: - return (size, name) + return size, name - def _torrents_sort(self, torrents: Any, dl_type: str): + @staticmethod + def _torrents_sort(torrents: Any, dl_type: str): """ 按种子添加时间排序 """ @@ -325,7 +346,8 @@ class DownloadSiteTag(_PluginBase): torrents = sorted(torrents, key=lambda x: x.added_date, reverse=False) return torrents - def _get_hash(self, torrent: Any, dl_type: str): + @staticmethod + def _get_hash(torrent: Any, dl_type: str): """ 获取种子hash """ @@ -335,7 +357,8 @@ class DownloadSiteTag(_PluginBase): print(str(e)) return "" - def _get_trackers(self, torrent: Any, dl_type: str): + @staticmethod + def _get_trackers(torrent: Any, dl_type: str): """ 获取种子trackers """ @@ -351,7 +374,8 @@ class DownloadSiteTag(_PluginBase): num_downloaded 整数 跟踪器报告的当前 torrent 的已完成下载次数 msg 字符串 跟踪器消息(无法知道此消息是什么 - 由跟踪器管理员决定) """ - return [tracker.get("url") for tracker in (torrent.trackers or []) if tracker.get("tier", -1) >= 0 and tracker.get("url")] + return [tracker.get("url") for tracker in (torrent.trackers or []) if + tracker.get("tier", -1) >= 0 and tracker.get("url")] else: """ class Tracker(Container): @@ -371,12 +395,14 @@ class DownloadSiteTag(_PluginBase): def tier(self) -> int: return self.fields["tier"] """ - return [tracker.announce for tracker in (torrent.trackers or []) if tracker.tier >= 0 and tracker.announce] + return [tracker.announce for tracker in (torrent.trackers or []) if + tracker.tier >= 0 and tracker.announce] except Exception as e: print(str(e)) return [] - def _get_label(self, torrent: Any, dl_type: str): + @staticmethod + def _get_label(torrent: Any, dl_type: str): """ 获取种子标签 """ @@ -387,7 +413,8 @@ class DownloadSiteTag(_PluginBase): print(str(e)) return [] - def _get_category(self, torrent: Any, dl_type: str): + @staticmethod + def _get_category(torrent: Any, dl_type: str): """ 获取种子分类 """ @@ -397,20 +424,23 @@ class DownloadSiteTag(_PluginBase): print(str(e)) return None - def _set_torrent_info(self, DOWNLOADER: str, _hash: str, _torrent: Any = None, _tags: list = [], _cat: str = None, _original_tags: list = None): + def _set_torrent_info(self, DOWNLOADER: str, _hash: str, _torrent: Any = None, _tags=None, _cat: str = None, + _original_tags: list = None): """ 设置种子标签与分类 """ # 当前下载器 + if _tags is None: + _tags = [] downloader_obj = self._get_downloader(DOWNLOADER) if not _torrent: _torrent, error = downloader_obj.get_torrents(ids=_hash) if not _torrent or error: logger.error( - f"{self.LOG_TAG}设置种子标签与分类时发生了错误: 通过 {_hash} 查询不到任何种子!") + f"{self.LOG_TAG}设置种子标签与分类时发生了错误: 通过 {_hash} 查询不到任何种子!") return logger.info( - f"{self.LOG_TAG}设置种子标签与分类: {_hash} 查询到 {len(_torrent)} 个种子") + f"{self.LOG_TAG}设置种子标签与分类: {_hash} 查询到 {len(_torrent)} 个种子") _torrent = _torrent[0] # 判断是否可执行 if DOWNLOADER and downloader_obj and _hash and _torrent: @@ -424,20 +454,23 @@ class DownloadSiteTag(_PluginBase): # 尝试设置种子分类, 如果失败, 则创建再设置一遍 try: _torrent.setCategory(category=_cat) - except: + except Exception as e: + logger.warn(f"下载器 {DOWNLOADER} 种子id: {_hash} 设置分类 {_cat} 失败:{str(e)}, " + f"尝试创建分类再设置 ...") downloader_obj.qbc.torrents_createCategory(name=_cat) _torrent.setCategory(category=_cat) else: # 设置标签 if _tags: # _original_tags = None表示未指定, 因此需要获取原始标签 - if _original_tags == None: + if _original_tags is None: _original_tags = self._get_label(torrent=_torrent, dl_type=DOWNLOADER) # 如果原始标签不是空的, 那么合并原始标签 if _original_tags: _tags = list(set(_original_tags).union(set(_tags))) downloader_obj.set_torrent_tag(ids=_hash, tags=_tags) - logger.warn(f"{self.LOG_TAG}下载器: {DOWNLOADER} 种子id: {_hash} {(' 标签: ' + ','.join(_tags)) if _tags else ''} {(' 分类: ' + _cat) if _cat else ''}") + logger.warn( + f"{self.LOG_TAG}下载器: {DOWNLOADER} 种子id: {_hash} {(' 标签: ' + ','.join(_tags)) if _tags else ''} {(' 分类: ' + _cat) if _cat else ''}") @eventmanager.register(EventType.DownloadAdded) def DownloadAdded(self, event: Event): @@ -449,7 +482,7 @@ class DownloadSiteTag(_PluginBase): if not event.event_data: return - + try: context: Context = event.event_data.get("context") _hash = event.event_data.get("hash") @@ -473,7 +506,6 @@ class DownloadSiteTag(_PluginBase): logger.error( f"{self.LOG_TAG}分析下载事件时发生了错误: {str(e)}") - def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/episodegroupmeta/__init__.py b/plugins/episodegroupmeta/__init__.py index 4147f3b..2263684 100644 --- a/plugins/episodegroupmeta/__init__.py +++ b/plugins/episodegroupmeta/__init__.py @@ -5,8 +5,10 @@ import time from pathlib import Path from typing import Any, List, Dict, Tuple, Optional, Union +from pydantic import BaseModel from requests import RequestException +from app import schemas from app.chain.mediaserver import MediaServerChain from app.core.config import settings from app.core.event import eventmanager, Event @@ -15,16 +17,12 @@ from app.log import logger from app.modules.emby import Emby from app.modules.jellyfin import Jellyfin from app.modules.plex import Plex +from app.modules.themoviedb.tmdbv3api import TV from app.plugins import _PluginBase -from app import schemas -from app.schemas.types import EventType, MediaType +from app.schemas.types import EventType from app.utils.common import retry from app.utils.http import RequestUtils -from app.modules.themoviedb.tmdbv3api import TV - -from pydantic import BaseModel - class ExistMediaInfo(BaseModel): # 类型 电影、电视剧 @@ -49,7 +47,7 @@ class EpisodeGroupMeta(_PluginBase): # 主题色 plugin_color = "#098663" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "叮叮当" # 作者主页 @@ -67,6 +65,10 @@ class EpisodeGroupMeta(_PluginBase): # 私有属性 mschain = None tv = None + emby = None + plex = None + jellyfin = None + _enabled = False _ignorelock = False _delay = 0 @@ -75,6 +77,9 @@ class EpisodeGroupMeta(_PluginBase): def init_plugin(self, config: dict = None): self.mschain = MediaServerChain() self.tv = TV() + self.emby = Emby() + self.plex = Plex() + self.jellyfin = Jellyfin() if config: self._enabled = config.get("enabled") self._ignorelock = config.get("ignorelock") @@ -259,7 +264,8 @@ class EpisodeGroupMeta(_PluginBase): if not mediainfo.tmdb_id: self.log_warn(f"{mediainfo.title} 没有tmdbID, 无需处理") return - if len(self._allowlist) != 0 and not mediainfo.title in self._allowlist: + if len(self._allowlist) != 0 \ + and mediainfo.title not in self._allowlist: self.log_warn(f"{mediainfo.title} 不在白名单, 无需处理") return # 获取剧集组信息 @@ -335,8 +341,8 @@ class EpisodeGroupMeta(_PluginBase): # 是否无视项目锁定 if not self._ignorelock: if iteminfo.get("LockData") or ( - "Name" in iteminfo.get("LockedFields", []) and "Overview" in iteminfo.get( - "LockedFields", [])): + "Name" in iteminfo.get("LockedFields", []) + and "Overview" in iteminfo.get("LockedFields", [])): self.log_warn(f"已锁定媒体项 - itemid: {_id}, 第 {order} 季, 第 {ep_num} 集") continue # 替换项目数据 @@ -371,7 +377,8 @@ class EpisodeGroupMeta(_PluginBase): self.log_info(f"{mediainfo.title_year} 已经运行完毕了..") - def __append_to_list(self, list, item): + @staticmethod + def __append_to_list(list, item): if item not in list: list.append(item) @@ -387,15 +394,15 @@ class EpisodeGroupMeta(_PluginBase): # 获取系列id item_id = None try: - res = Emby().get_data(("[HOST]emby/Items?" - "IncludeItemTypes=Series" - "&Fields=ProductionYear" - "&StartIndex=0" - "&Recursive=true" - "&SearchTerm=%s" - "&Limit=10" - "&IncludeSearchTypes=false" - "&api_key=[APIKEY]") % (mediainfo.title)) + res = self.emby.get_data(("[HOST]emby/Items?" + "IncludeItemTypes=Series" + "&Fields=ProductionYear" + "&StartIndex=0" + "&Recursive=true" + "&SearchTerm=%s" + "&Limit=10" + "&IncludeSearchTypes=false" + "&api_key=[APIKEY]") % mediainfo.title) res_items = res.json().get("Items") if res_items: for res_item in res_items: @@ -407,15 +414,15 @@ class EpisodeGroupMeta(_PluginBase): if not item_id: return None # 验证tmdbid是否相同 - item_info = Emby().get_iteminfo(item_id) + item_info = self.emby.get_iteminfo(item_id) if item_info: if mediainfo.tmdb_id and item_info.tmdbid: if str(mediainfo.tmdb_id) != str(item_info.tmdbid): self.log_error(f"tmdbid不匹配或不存在") return None try: - res_json = Emby().get_data( - "[HOST]emby/Shows/%s/Episodes?Season=&IsMissing=false&api_key=[APIKEY]" % (item_id)) + res_json = self.emby.get_data( + "[HOST]emby/Shows/%s/Episodes?Season=&IsMissing=false&api_key=[APIKEY]" % item_id) if res_json: tv_item = res_json.json() res_items = tv_item.get("Items") @@ -452,9 +459,10 @@ class EpisodeGroupMeta(_PluginBase): # 获取系列id item_id = None try: - res = Jellyfin.get_data(("[HOST]Users/[USER]/Items?" - "api_key=[APIKEY]&searchTerm=%s&IncludeItemTypes=Series&Limit=10&Recursive=true") % ( - mediainfo.title)) + res = self.jellyfin.get_data(url=f"[HOST]Users/[USER]/Items?api_key=[APIKEY]" + f"&searchTerm={mediainfo.title}" + f"&IncludeItemTypes=Series" + f"&Limit=10&Recursive=true") res_items = res.json().get("Items") if res_items: for res_item in res_items: @@ -466,15 +474,15 @@ class EpisodeGroupMeta(_PluginBase): if not item_id: return None # 验证tmdbid是否相同 - item_info = Jellyfin().get_iteminfo(item_id) + item_info = self.jellyfin.get_iteminfo(item_id) if item_info: if mediainfo.tmdb_id and item_info.tmdbid: if str(mediainfo.tmdb_id) != str(item_info.tmdbid): self.log_error(f"tmdbid不匹配或不存在") return None try: - res_json = Jellyfin().get_data( - "[HOST]emby/Shows/%s/Episodes?Season=&IsMissing=false&api_key=[APIKEY]" % (item_id)) + res_json = self.jellyfin.get_data( + "[HOST]emby/Shows/%s/Episodes?Season=&IsMissing=false&api_key=[APIKEY]" % item_id) if res_json: tv_item = res_json.json() res_items = tv_item.get("Items") @@ -509,7 +517,7 @@ class EpisodeGroupMeta(_PluginBase): def __plex_media_exists(): try: - _plex = Plex().get_plex() + _plex = self.plex.get_plex() if not _plex: return None if existsinfo.itemid: @@ -609,7 +617,7 @@ class EpisodeGroupMeta(_PluginBase): try: url = f'[HOST]emby/Users/[USER]/Items/{itemid}?' \ f'Fields=ChannelMappingInfo&api_key=[APIKEY]' - res = Emby().get_data(url=url) + res = self.emby.get_data(url=url) if res: return res.json() except Exception as err: @@ -622,7 +630,7 @@ class EpisodeGroupMeta(_PluginBase): """ try: url = f'[HOST]Users/[USER]/Items/{itemid}?Fields=ChannelMappingInfo&api_key=[APIKEY]' - res = Jellyfin().get_data(url=url) + res = self.jellyfin.get_data(url=url) if res: result = res.json() if result: @@ -638,7 +646,7 @@ class EpisodeGroupMeta(_PluginBase): """ iteminfo = {} try: - plexitem = Plex().get_plex().library.fetchItem(ekey=itemid) + plexitem = self.plex.get_plex().library.fetchItem(ekey=itemid) if 'movie' in plexitem.METADATA_TYPE: iteminfo['Type'] = 'Movie' iteminfo['IsFolder'] = False @@ -667,11 +675,13 @@ class EpisodeGroupMeta(_PluginBase): if plexitem.title.locked: iteminfo['LockedFields'].append('Name') except Exception as err: + logger.warn(f"获取Plex媒体项详情失败:{str(err)}") pass try: if plexitem.summary.locked: iteminfo['LockedFields'].append('Overview') except Exception as err: + logger.warn(f"获取Plex媒体项详情失败:{str(err)}") pass return iteminfo except Exception as err: @@ -695,7 +705,7 @@ class EpisodeGroupMeta(_PluginBase): 更新Emby媒体项详情 """ try: - res = Emby().post_data( + res = self.emby.post_data( url=f'[HOST]emby/Items/{itemid}?api_key=[APIKEY]&reqformat=json', data=json.dumps(iteminfo), headers={ @@ -716,7 +726,7 @@ class EpisodeGroupMeta(_PluginBase): 更新Jellyfin媒体项详情 """ try: - res = Jellyfin().post_data( + res = self.jellyfin.post_data( url=f'[HOST]Items/{itemid}?api_key=[APIKEY]', data=json.dumps(iteminfo), headers={ @@ -737,7 +747,7 @@ class EpisodeGroupMeta(_PluginBase): 更新Plex媒体项详情 """ try: - plexitem = Plex().get_plex().library.fetchItem(ekey=itemid) + plexitem = self.plex.get_plex().library.fetchItem(ekey=itemid) if 'CommunityRating' in iteminfo and iteminfo['CommunityRating']: edits = { 'audienceRating.value': iteminfo['CommunityRating'], @@ -788,7 +798,7 @@ class EpisodeGroupMeta(_PluginBase): """ try: url = f'[HOST]emby/Items/{itemid}/Images/Primary?api_key=[APIKEY]' - res = Emby().post_data( + res = self.emby.post_data( url=url, data=_base64, headers={ @@ -812,7 +822,7 @@ class EpisodeGroupMeta(_PluginBase): try: url = f'[HOST]Items/{itemid}/RemoteImages/Download?' \ f'Type=Primary&ImageUrl={imageurl}&ProviderName=TheMovieDb&api_key=[APIKEY]' - res = Jellyfin().post_data(url=url) + res = self.jellyfin.post_data(url=url) if res and res.status_code in [200, 204]: return True else: @@ -828,7 +838,7 @@ class EpisodeGroupMeta(_PluginBase): # FIXME 改为预下载图片 """ try: - plexitem = Plex().get_plex().library.fetchItem(ekey=itemid) + plexitem = self.plex.get_plex().library.fetchItem(ekey=itemid) plexitem.uploadPoster(url=imageurl) return True except Exception as err: diff --git a/plugins/invitessignin/__init__.py b/plugins/invitessignin/__init__.py index 2a906b5..059e3b8 100644 --- a/plugins/invitessignin/__init__.py +++ b/plugins/invitessignin/__init__.py @@ -22,7 +22,7 @@ class InvitesSignin(_PluginBase): # 插件图标 plugin_icon = "invites.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.3" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -56,33 +56,22 @@ class InvitesSignin(_PluginBase): self._notify = config.get("notify") self._onlyonce = config.get("onlyonce") - # 加载模块 - if self._enabled: + if self._onlyonce: # 定时服务 self._scheduler = BackgroundScheduler(timezone=settings.TZ) - - if self._cron: - try: - self._scheduler.add_job(func=self.__signin, - trigger=CronTrigger.from_crontab(self._cron), - name="药丸签到") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - - if self._onlyonce: - logger.info(f"药丸签到服务启动,立即运行一次") - self._scheduler.add_job(func=self.__signin, trigger='date', - run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), - name="药丸签到") - # 关闭一次性开关 - self._onlyonce = False - self.update_config({ - "onlyonce": False, - "cron": self._cron, - "enabled": self._enabled, - "cookie": self._cookie, - "notify": self._notify, - }) + logger.info(f"药丸签到服务启动,立即运行一次") + self._scheduler.add_job(func=self.__signin, trigger='date', + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), + name="药丸签到") + # 关闭一次性开关 + self._onlyonce = False + self.update_config({ + "onlyonce": False, + "cron": self._cron, + "enabled": self._enabled, + "cookie": self._cookie, + "notify": self._notify, + }) # 启动任务 if self._scheduler.get_jobs(): @@ -183,6 +172,27 @@ class InvitesSignin(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "InvitesSignin", + "name": "药丸签到服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.__signin, + "kwargs": {} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/iyuuautoseed/__init__.py b/plugins/iyuuautoseed/__init__.py index 0a4d8e3..11f1ec3 100644 --- a/plugins/iyuuautoseed/__init__.py +++ b/plugins/iyuuautoseed/__init__.py @@ -34,7 +34,7 @@ class IYUUAutoSeed(_PluginBase): # 插件图标 plugin_icon = "IYUU.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.3" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -128,22 +128,21 @@ class IYUUAutoSeed(_PluginBase): self.qb = Qbittorrent() self.tr = Transmission() - if self._cron: - try: - self._scheduler.add_job(self.auto_seed, - CronTrigger.from_crontab(self._cron)) - logger.info(f"辅种服务启动,周期:{self._cron}") - except Exception as err: - logger.error(f"辅种服务启动失败:{str(err)}") - self.systemmessage.put(f"辅种服务启动失败:{str(err)}") if self._onlyonce: logger.info(f"辅种服务启动,立即运行一次") self._scheduler.add_job(self.auto_seed, 'date', run_date=datetime.now( tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3) ) + # 关闭一次性开关 self._onlyonce = False + if self._scheduler.get_jobs(): + # 追加种子校验服务 + self._scheduler.add_job(self.check_recheck, 'interval', minutes=3) + # 启动服务 + self._scheduler.print_jobs() + self._scheduler.start() if self._clearcache: # 关闭清除缓存开关 @@ -153,13 +152,6 @@ class IYUUAutoSeed(_PluginBase): # 保存配置 self.__update_config() - if self._scheduler.get_jobs(): - # 追加种子校验服务 - self._scheduler.add_job(self.check_recheck, 'interval', minutes=3) - # 启动服务 - self._scheduler.print_jobs() - self._scheduler.start() - def get_state(self) -> bool: return True if self._enabled and self._cron and self._token and self._downloaders else False @@ -170,6 +162,27 @@ class IYUUAutoSeed(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self.get_state(): + return [{ + "id": "IYUUAutoSeed", + "name": "IYUU自动辅种服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.auto_seed, + "kwargs": {} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/libraryscraper/__init__.py b/plugins/libraryscraper/__init__.py index 7b78bb5..bbc03d6 100644 --- a/plugins/libraryscraper/__init__.py +++ b/plugins/libraryscraper/__init__.py @@ -25,7 +25,7 @@ class LibraryScraper(_PluginBase): # 插件图标 plugin_icon = "scraper.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.3" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -67,23 +67,10 @@ class LibraryScraper(_PluginBase): # 启动定时任务 & 立即运行一次 if self._enabled or self._onlyonce: self.transferhis = TransferHistoryOper() - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - if self._cron: - logger.info(f"媒体库刮削服务启动,周期:{self._cron}") - try: - self._scheduler.add_job(func=self.__libraryscraper, - trigger=CronTrigger.from_crontab(self._cron), - name="媒体库刮削") - except Exception as e: - logger.error(f"媒体库刮削服务启动失败,原因:{str(e)}") - self.systemmessage.put(f"媒体库刮削服务启动失败,原因:{str(e)}") - else: - logger.info(f"媒体库刮削服务启动,周期:每7天") - self._scheduler.add_job(func=self.__libraryscraper, - trigger=CronTrigger.from_crontab("0 0 */7 * *"), - name="媒体库刮削") + if self._onlyonce: logger.info(f"媒体库刮削服务,立即运行一次") + self._scheduler = BackgroundScheduler(timezone=settings.TZ) self._scheduler.add_job(func=self.__libraryscraper, trigger='date', run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), name="媒体库刮削") @@ -97,10 +84,10 @@ class LibraryScraper(_PluginBase): "scraper_paths": self._scraper_paths, "exclude_paths": self._exclude_paths }) - if self._scheduler.get_jobs(): - # 启动服务 - self._scheduler.print_jobs() - self._scheduler.start() + if self._scheduler.get_jobs(): + # 启动服务 + self._scheduler.print_jobs() + self._scheduler.start() def get_state(self) -> bool: return self._enabled @@ -112,6 +99,35 @@ class LibraryScraper(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "LibraryScraper", + "name": "媒体库刮削", + "trigger": "cron", + "func": self.__libraryscraper, + "kwargs": {} + }] + elif self._enabled: + return [{ + "id": "LibraryScraper", + "name": "媒体库刮削", + "trigger": CronTrigger.from_crontab("0 0 */7 * *"), + "func": self.__libraryscraper, + "kwargs": {} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: return [ { diff --git a/plugins/linkmonitor/__init__.py b/plugins/linkmonitor/__init__.py index 8ccc5a8..a02d97a 100644 --- a/plugins/linkmonitor/__init__.py +++ b/plugins/linkmonitor/__init__.py @@ -51,7 +51,7 @@ class LinkMonitor(_PluginBase): # 插件图标 plugin_icon = "Linkace_C.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.3" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -104,8 +104,6 @@ class LinkMonitor(_PluginBase): self.stop_service() if self._enabled or self._onlyonce: - # 定时服务管理器 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) # 读取目录配置 monitor_dirs = self._monitor_dirs.split("\n") @@ -176,6 +174,8 @@ class LinkMonitor(_PluginBase): # 运行一次定时服务 if self._onlyonce: + # 定时服务管理器 + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info("目录监控服务启动,立即运行一次") self._scheduler.add_job(func=self.sync_all, trigger='date', run_date=datetime.datetime.now( @@ -186,21 +186,10 @@ class LinkMonitor(_PluginBase): # 保存配置 self.__update_config() - # 全量同步定时 - if self._enabled and self._cron: - try: - self._scheduler.add_job(func=self.sync_all, - trigger=CronTrigger.from_crontab(self._cron), - name="实时硬链接") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - - # 启动定时服务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() + # 启动定时服务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() def __update_config(self): """ @@ -380,6 +369,26 @@ class LinkMonitor(_PluginBase): "description": "实时硬链接", }] + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "LinkMonitor", + "name": "全量硬链接定时服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.sync_all, + "kwargs": {} + }] + def sync(self) -> schemas.Response: """ API调用目录同步 diff --git a/plugins/mediaserverrefresh/__init__.py b/plugins/mediaserverrefresh/__init__.py index 43eb2fd..cc6578b 100644 --- a/plugins/mediaserverrefresh/__init__.py +++ b/plugins/mediaserverrefresh/__init__.py @@ -21,7 +21,7 @@ class MediaServerRefresh(_PluginBase): # 插件图标 plugin_icon = "refresh2.png" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -36,8 +36,14 @@ class MediaServerRefresh(_PluginBase): # 私有属性 _enabled = False _delay = 0 + _emby = None + _jellyfin = None + _plex = None def init_plugin(self, config: dict = None): + self._emby = Emby() + self._jellyfin = Jellyfin() + self._plex = Plex() if config: self._enabled = config.get("enabled") self._delay = config.get("delay") or 0 @@ -146,16 +152,16 @@ class MediaServerRefresh(_PluginBase): ] # Emby if "emby" in settings.MEDIASERVER: - Emby().refresh_library_by_items(items) + self._emby.refresh_library_by_items(items) # Jeyllyfin if "jellyfin" in settings.MEDIASERVER: # FIXME Jellyfin未找到刷新单个项目的API - Jellyfin().refresh_root_library() + self._jellyfin.refresh_root_library() # Plex if "plex" in settings.MEDIASERVER: - Plex().refresh_library_by_items(items) + self._plex.refresh_library_by_items(items) def stop_service(self): """ diff --git a/plugins/mediasyncdel/__init__.py b/plugins/mediasyncdel/__init__.py index d63ee86..3175e4d 100644 --- a/plugins/mediasyncdel/__init__.py +++ b/plugins/mediasyncdel/__init__.py @@ -31,7 +31,7 @@ class MediaSyncDel(_PluginBase): # 插件图标 plugin_icon = "mediasyncdel.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.3" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -96,27 +96,6 @@ class MediaSyncDel(_PluginBase): "library_path": self._library_path }) - if self._enabled and str(self._sync_type) == "log": - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - # 媒体库同步删除日志方式 - if self._cron: - try: - self._scheduler.add_job(func=self.sync_del_by_log, - trigger=CronTrigger.from_crontab(self._cron), - name="媒体库同步删除日志方式") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - else: - self._scheduler.add_job(self.sync_del_by_log, "interval", minutes=30, - name="媒体库同步删除日志方式") - - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() - @staticmethod def get_command() -> List[Dict[str, Any]]: """ @@ -128,6 +107,37 @@ class MediaSyncDel(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and str(self._sync_type) == "log": + # 媒体库同步删除日志方式 + if self._cron: + return [{ + "id": "MediaSyncDel", + "name": "媒体库同步删除服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.sync_del_by_log, + "kwargs": {} + }] + else: + return [{ + "id": "MediaSyncDel", + "name": "媒体库同步删除服务", + "trigger": "interval", + "func": self.sync_del_by_log, + "kwargs": {"minutes": 30} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 @@ -1325,7 +1335,7 @@ class MediaSyncDel(_PluginBase): 获取emby日志列表、解析emby日志 """ - def __parse_log(file_name: str, del_list: list, last_time): + def __parse_log(file_name: str, del_list: list): """ 解析emby日志 """ @@ -1419,8 +1429,7 @@ class MediaSyncDel(_PluginBase): log_files.reverse() for log_file in log_files: del_medias = __parse_log(file_name=log_file, - del_list=del_medias, - last_time=last_time) + del_list=del_medias) return del_medias @@ -1430,7 +1439,7 @@ class MediaSyncDel(_PluginBase): 获取jellyfin日志列表、解析jellyfin日志 """ - def __parse_log(file_name: str, del_list: list, last_time): + def __parse_log(file_name: str, del_list: list): """ 解析jellyfin日志 """ @@ -1524,8 +1533,7 @@ class MediaSyncDel(_PluginBase): log_files.reverse() for log_file in log_files: del_medias = __parse_log(file_name=log_file, - del_list=del_medias, - last_time=last_time) + del_list=del_medias) return del_medias diff --git a/plugins/moviepilotupdatenotify/__init__.py b/plugins/moviepilotupdatenotify/__init__.py index 0f165bb..46d9e5b 100644 --- a/plugins/moviepilotupdatenotify/__init__.py +++ b/plugins/moviepilotupdatenotify/__init__.py @@ -22,7 +22,7 @@ class MoviePilotUpdateNotify(_PluginBase): # 插件图标 plugin_icon = "Moviepilot_A.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -54,24 +54,6 @@ class MoviePilotUpdateNotify(_PluginBase): self._restart = config.get("restart") self._notify = config.get("notify") - # 加载模块 - if self._enabled: - # 定时服务 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - - if self._cron: - try: - self._scheduler.add_job(func=self.__check_update, - trigger=CronTrigger.from_crontab(self._cron), - name="检查MoviePilot更新") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() - def __check_update(self): """ 检查MoviePilot更新 @@ -136,6 +118,29 @@ class MoviePilotUpdateNotify(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [ + { + "id": "MoviePilotUpdateNotify", + "name": "MoviePilot更新检查服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.__check_update, + "kwargs": {} + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/personmeta/__init__.py b/plugins/personmeta/__init__.py index f6d6bd8..5c769fb 100644 --- a/plugins/personmeta/__init__.py +++ b/plugins/personmeta/__init__.py @@ -39,7 +39,7 @@ class PersonMeta(_PluginBase): # 插件图标 plugin_icon = "actor.png" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -80,31 +80,19 @@ class PersonMeta(_PluginBase): self.stop_service() # 启动服务 - if self._enabled or self._onlyonce: + if self._onlyonce: self._scheduler = BackgroundScheduler(timezone=settings.TZ) - if self._cron or self._onlyonce: - if self._cron: - try: - self._scheduler.add_job(func=self.scrap_library, - trigger=CronTrigger.from_crontab(self._cron), - name="演职人员刮削") - logger.info(f"演职人员刮削服务启动,周期:{self._cron}") - except Exception as e: - logger.error(f"演职人员刮削服务启动失败,错误信息:{str(e)}") - self.systemmessage.put(f"演职人员刮削服务启动失败,错误信息:{str(e)}") - if self._onlyonce: - self._scheduler.add_job(func=self.scrap_library, trigger='date', - run_date=datetime.datetime.now( - tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) - ) - logger.info(f"演职人员刮削服务启动,立即运行一次") - # 关闭一次性开关 - self._onlyonce = False - # 保存配置 - self.__update_config() - + self._scheduler.add_job(func=self.scrap_library, trigger='date', + run_date=datetime.datetime.now( + tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) + ) + logger.info(f"演职人员刮削服务启动,立即运行一次") + # 关闭一次性开关 + self._onlyonce = False + # 保存配置 + self.__update_config() + # 启动服务 if self._scheduler.get_jobs(): - # 启动服务 self._scheduler.print_jobs() self._scheduler.start() @@ -131,6 +119,26 @@ class PersonMeta(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "PersonMeta", + "name": "演职人员刮削服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.scrap_library, + "kwargs": {} + }] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/removelink/__init__.py b/plugins/removelink/__init__.py index bb762cb..cc90f5b 100644 --- a/plugins/removelink/__init__.py +++ b/plugins/removelink/__init__.py @@ -25,26 +25,26 @@ class FileMonitorHandler(FileSystemEventHandler): def on_created(self, event): logger.info("监测到新增文件:%s" % event.src_path) - if self.sync._exclude_keywords: - for keyword in self.sync._exclude_keywords.split("\n"): + if self.sync.exclude_keywords: + for keyword in self.sync.exclude_keywords.split("\n"): if keyword and re.findall(keyword, event.src_path): logger.info(f"{event.src_path} 命中过滤关键字 {keyword},不处理") print(f"{event.src_path} 命中过滤关键字 {keyword},不处理") return new_file = Path(event.src_path) try: - self.sync._state_set.add((Path(event.src_path), new_file.stat().st_ino)) + self.sync.state_set.add((Path(event.src_path), new_file.stat().st_ino)) except Exception as e: - logger.error("文件丢失:%s" % event.src_path) + logger.error(f"文件丢失:%s - {e}" % event.src_path) def on_deleted(self, event): - if Path(event.src_path) in self.sync._ignored_files: - self.sync._ignored_files.remove(Path(event.src_path)) + if Path(event.src_path) in self.sync.ignored_files: + self.sync.ignored_files.remove(Path(event.src_path)) return logger.info("监测到删除:%s" % event.src_path) # 命中过滤关键字不处理 - if self.sync._exclude_keywords: - for keyword in self.sync._exclude_keywords.split("\n"): + if self.sync.exclude_keywords: + for keyword in self.sync.exclude_keywords.split("\n"): if keyword and re.findall(keyword, event.src_path): logger.info(f"{event.src_path} 命中过滤关键字 {keyword},不处理") print(f"{event.src_path} 命中过滤关键字 {keyword},不处理") @@ -80,7 +80,7 @@ class RemoveLink(_PluginBase): # 插件图标 plugin_icon = "Ombi_A.png" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "DzAvril" # 作者主页 @@ -93,27 +93,27 @@ class RemoveLink(_PluginBase): auth_level = 1 # preivate property - _monitor_dirs = "" - _exclude_keywords = "" + monitor_dirs = "" + exclude_keywords = "" _enabled = False _notify = False _observer = [] - _state_set = set() - _ignored_files = set() + state_set = set() + ignored_files = set() def init_plugin(self, config: dict = None): logger.info(f"Hello, RemoveLink! config {config}") if config: self._enabled = config.get("enabled") self._notify = config.get("notify") - self._monitor_dirs = config.get("monitor_dirs") - self._exclude_keywords = config.get("exclude_keywords") or "" + self.monitor_dirs = config.get("monitor_dirs") + self.exclude_keywords = config.get("exclude_keywords") or "" self.__update_config() # 停止现有任务 self.stop_service() if self._enabled: # 读取目录配置 - monitor_dirs = self._monitor_dirs.split("\n") + monitor_dirs = self.monitor_dirs.split("\n") logger.info(f"监控目录:{monitor_dirs}") if not monitor_dirs: return @@ -134,7 +134,7 @@ class RemoveLink(_PluginBase): err_msg = str(e) logger.error(f"{mon_path} 启动目录监控失败:{err_msg}") self.systemmessage.put(f"{mon_path} 启动目录监控失败:{err_msg}") - self._state_set = updateState(monitor_dirs) + self.state_set = updateState(monitor_dirs) def __update_config(self): """ @@ -144,8 +144,8 @@ class RemoveLink(_PluginBase): { "enabled": self._enabled, "notify": self._notify, - "monitor_dirs": self._monitor_dirs, - "exclude_keywords": self._exclude_keywords, + "monitor_dirs": self.monitor_dirs, + "exclude_keywords": self.exclude_keywords, } ) @@ -289,15 +289,15 @@ class RemoveLink(_PluginBase): """ 处理删除事件 """ - current_set = updateState(self._monitor_dirs.split("\n")) - deleted_set = self._state_set - current_set + current_set = updateState(self.monitor_dirs.split("\n")) + deleted_set = self.state_set - current_set deleted_inode = [x[1] for x in deleted_set] try: # 在current_set中查找与deleted_inode有相同inode的文件并删除 for path, inode in current_set: if inode in deleted_inode: file = Path(path) - self._ignored_files.add(file) + self.ignored_files.add(file) file.unlink() logger.info(f"删除硬链接文件:{path}") if self._notify: @@ -316,4 +316,4 @@ class RemoveLink(_PluginBase): except Exception as e: logger.error("目录监控发生错误:%s - %s" % (str(e), traceback.format_exc())) - self._state_set = updateState(self._monitor_dirs.split("\n")) + self.state_set = updateState(self.monitor_dirs.split("\n")) diff --git a/plugins/rsssubscribe/__init__.py b/plugins/rsssubscribe/__init__.py index 27149b3..0332f1d 100644 --- a/plugins/rsssubscribe/__init__.py +++ b/plugins/rsssubscribe/__init__.py @@ -30,7 +30,7 @@ class RssSubscribe(_PluginBase): # 插件图标 plugin_icon = "rss.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -89,43 +89,29 @@ class RssSubscribe(_PluginBase): self._action = config.get("action") self._save_path = config.get("save_path") - if self._enabled or self._onlyonce: - + if self._onlyonce: self._scheduler = BackgroundScheduler(timezone=settings.TZ) - if self._cron: - try: - self._scheduler.add_job(func=self.check, - trigger=CronTrigger.from_crontab(self._cron), - name="RSS订阅") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - else: - self._scheduler.add_job(self.check, "interval", minutes=30, name="RSS订阅") - - if self._onlyonce: - logger.info(f"RSS订阅服务启动,立即运行一次") - self._scheduler.add_job(func=self.check, trigger='date', - run_date=datetime.datetime.now( - tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) - ) - - if self._onlyonce or self._clear: - # 关闭一次性开关 - self._onlyonce = False - # 记录清理缓存设置 - self._clearflag = self._clear - # 关闭清理缓存开关 - self._clear = False - # 保存设置 - self.__update_config() + logger.info(f"自定义订阅服务启动,立即运行一次") + self._scheduler.add_job(func=self.check, trigger='date', + run_date=datetime.datetime.now( + tz=pytz.timezone(settings.TZ)) + datetime.timedelta(seconds=3) + ) # 启动任务 if self._scheduler.get_jobs(): self._scheduler.print_jobs() self._scheduler.start() + if self._onlyonce or self._clear: + # 关闭一次性开关 + self._onlyonce = False + # 记录清理缓存设置 + self._clearflag = self._clear + # 关闭清理缓存开关 + self._clear = False + # 保存设置 + self.__update_config() + def get_state(self) -> bool: return self._enabled @@ -149,6 +135,35 @@ class RssSubscribe(_PluginBase): """ pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "RssSubscribe", + "name": "自定义订阅服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.check, + "kwargs": {} + }] + elif self._enabled: + return [{ + "id": "RssSubscribe", + "name": "自定义订阅服务", + "trigger": "interval", + "func": self.check, + "kwargs": {"minutes": 30} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/siterefresh/__init__.py b/plugins/siterefresh/__init__.py index c521da3..48ccfe6 100644 --- a/plugins/siterefresh/__init__.py +++ b/plugins/siterefresh/__init__.py @@ -17,7 +17,7 @@ class SiteRefresh(_PluginBase): # 插件图标 plugin_icon = "Chrome_A.png" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -77,7 +77,7 @@ class SiteRefresh(_PluginBase): site_name = site.name logger.info(f"开始尝试登录站点 {site_name}") - siteurl, siteuser, sitepwd = None, None, None + siteurl, siteuser, sitepwd, sitecode = None, None, None, None # 判断site是否已配置用户名密码 for site_conf in self._siteconf: if not site_conf: diff --git a/plugins/sitestatistic/__init__.py b/plugins/sitestatistic/__init__.py index 89f4a67..cec39b5 100644 --- a/plugins/sitestatistic/__init__.py +++ b/plugins/sitestatistic/__init__.py @@ -43,7 +43,7 @@ class SiteStatistic(_PluginBase): # 插件图标 plugin_icon = "statistic.png" # 插件版本 - plugin_version = "1.6" + plugin_version = "1.7" # 插件作者 plugin_author = "lightolly" # 作者主页 @@ -101,9 +101,6 @@ class SiteStatistic(_PluginBase): self._site_schema = ModuleHelper.load('app.plugins.sitestatistic.siteuserinfo', filter_func=lambda _, obj: hasattr(obj, 'schema')) - # 定时服务 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - self._site_schema.sort(key=lambda x: x.order) # 站点上一次更新时间 self._last_update_time = None @@ -112,6 +109,8 @@ class SiteStatistic(_PluginBase): # 立即运行一次 if self._onlyonce: + # 定时服务 + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info(f"站点数据统计服务启动,立即运行一次") self._scheduler.add_job(self.refresh_all_site_data, 'date', run_date=datetime.now( @@ -123,31 +122,10 @@ class SiteStatistic(_PluginBase): # 保存配置 self.__update_config() - # 周期运行 - if self._enabled and self._cron: - try: - self._scheduler.add_job(func=self.refresh_all_site_data, - trigger=CronTrigger.from_crontab(self._cron), - name="站点数据统计") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - # 推送实时消息 - self.systemmessage.put(f"执行周期配置错误:{str(err)}") - else: - triggers = TimerUtils.random_scheduler(num_executions=1, - begin_hour=0, - end_hour=1, - min_interval=1, - max_interval=60) - for trigger in triggers: - self._scheduler.add_job(self.refresh_all_site_data, "cron", - hour=trigger.hour, minute=trigger.minute, - name="站点数据统计") - - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() def get_state(self) -> bool: return self._enabled @@ -186,6 +164,46 @@ class SiteStatistic(_PluginBase): "description": "刷新对应域名的站点数据", }] + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._cron: + return [{ + "id": "SiteStatistic", + "name": "站点数据统计服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.refresh_all_site_data, + "kwargs": {} + }] + elif self._enabled: + triggers = TimerUtils.random_scheduler(num_executions=1, + begin_hour=0, + end_hour=1, + min_interval=1, + max_interval=60) + ret_jobs = [] + for trigger in triggers: + ret_jobs.append({ + "id": f"SiteStatistic|{trigger.hour}:{trigger.minute}", + "name": "站点数据统计服务", + "trigger": "cron", + "func": self.refresh_all_site_data, + "kwargs": { + "hour": trigger.hour, + "minute": trigger.minute + } + }) + return ret_jobs + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/speedlimiter/__init__.py b/plugins/speedlimiter/__init__.py index cf5a804..4700c6c 100644 --- a/plugins/speedlimiter/__init__.py +++ b/plugins/speedlimiter/__init__.py @@ -1,8 +1,6 @@ import ipaddress from typing import List, Tuple, Dict, Any -from apscheduler.schedulers.background import BackgroundScheduler - from app.core.config import settings from app.core.event import eventmanager, Event from app.log import logger @@ -25,7 +23,7 @@ class SpeedLimiter(_PluginBase): # 插件图标 plugin_icon = "Librespeed_A.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "Shurelol" # 作者主页 @@ -96,20 +94,6 @@ class SpeedLimiter(_PluginBase): if 'transmission' in self._downloader: self._tr = Transmission() - # 移出现有任务 - self.stop_service() - - # 启动限速任务 - if self._enabled and self._limit_enabled: - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - self._scheduler.add_job(func=self.check_playing_sessions, - trigger='interval', - seconds=self._interval, - name="播放限速检查") - self._scheduler.print_jobs() - self._scheduler.start() - logger.info("播放限速检查服务启动") - def get_state(self) -> bool: return self._enabled @@ -120,6 +104,29 @@ class SpeedLimiter(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self._enabled and self._limit_enabled and self._interval: + return [ + { + "id": "SpeedLimiter", + "name": "播放限速检查服务", + "trigger": "interval", + "func": self.check_playing_sessions, + "kwargs": {"seconds": self._interval} + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: return [ { @@ -614,14 +621,4 @@ class SpeedLimiter(_PluginBase): return False def stop_service(self): - """ - 退出插件 - """ - try: - if self._scheduler: - self._scheduler.remove_all_jobs() - if self._scheduler.running: - self._scheduler.shutdown() - self._scheduler = None - except Exception as e: - print(str(e)) + pass diff --git a/plugins/syncdownloadfiles/__init__.py b/plugins/syncdownloadfiles/__init__.py index 09c09a1..32be61e 100644 --- a/plugins/syncdownloadfiles/__init__.py +++ b/plugins/syncdownloadfiles/__init__.py @@ -22,7 +22,7 @@ class SyncDownloadFiles(_PluginBase): # 插件图标 plugin_icon = "Youtube-dl_A.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -88,27 +88,6 @@ class SyncDownloadFiles(_PluginBase): self.sync() - if self._enabled: - # 定时服务 - self._scheduler = BackgroundScheduler(timezone=settings.TZ) - if self._time: - try: - self._scheduler.add_job(func=self.sync, - trigger="interval", - hours=float(str(self._time).strip()), - name="自动同步下载器文件记录") - logger.info(f"自动同步下载器文件记录服务启动,时间间隔 {self._time} 小时") - except Exception as err: - logger.error(f"定时任务配置错误:{str(err)}") - - # 启动任务 - if self._scheduler.get_jobs(): - self._scheduler.print_jobs() - self._scheduler.start() - else: - self._enabled = False - self.__update_config() - def sync(self): """ 同步所选下载器种子记录 @@ -378,7 +357,7 @@ class SyncDownloadFiles(_PluginBase): return None def get_state(self) -> bool: - return self._enabled + return True if self._enabled and self._time else False @staticmethod def get_command() -> List[Dict[str, Any]]: @@ -387,6 +366,27 @@ class SyncDownloadFiles(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self.get_state(): + return [{ + "id": "SyncDownloadFiles", + "name": "同步下载器文件记录服务", + "trigger": "interval", + "func": self.sync, + "kwargs": {"seconds": float(str(self._time).strip()) * 3600} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 @@ -478,7 +478,7 @@ class SyncDownloadFiles(_PluginBase): 'component': 'VTextField', 'props': { 'model': 'time', - 'label': '同步时间间隔' + 'label': '同步时间间隔(小时)' } } ] diff --git a/plugins/torrentremover/__init__.py b/plugins/torrentremover/__init__.py index eaad1ec..07205dd 100644 --- a/plugins/torrentremover/__init__.py +++ b/plugins/torrentremover/__init__.py @@ -27,7 +27,7 @@ class TorrentRemover(_PluginBase): # 插件图标 plugin_icon = "delete.jpg" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -88,19 +88,10 @@ class TorrentRemover(_PluginBase): self.stop_service() if self.get_state() or self._onlyonce: - self._scheduler = BackgroundScheduler(timezone=settings.TZ) self.qb = Qbittorrent() self.tr = Transmission() - if self._cron: - try: - self._scheduler.add_job(func=self.delete_torrents, - trigger=CronTrigger.from_crontab(self._cron), - name="自动删种服务") - logger.info(f"自动删种服务启动,周期:{self._cron}") - except Exception as err: - logger.error(f"自动删种服务启动失败:{str(err)}") - self.systemmessage.put(f"自动删种服务启动失败:{str(err)}") if self._onlyonce: + self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info(f"自动删种服务启动,立即运行一次") self._scheduler.add_job(func=self.delete_torrents, trigger='date', run_date=datetime.now( @@ -130,10 +121,10 @@ class TorrentRemover(_PluginBase): "torrentcategorys": self._torrentcategorys }) - if self._scheduler.get_jobs(): - # 启动服务 - self._scheduler.print_jobs() - self._scheduler.start() + if self._scheduler.get_jobs(): + # 启动服务 + self._scheduler.print_jobs() + self._scheduler.start() def get_state(self) -> bool: return True if self._enabled and self._cron and self._downloaders else False @@ -145,6 +136,27 @@ class TorrentRemover(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self.get_state(): + return [{ + "id": "TorrentRemover", + "name": "自动删种服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.delete_torrents, + "kwargs": {} + }] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: return [ { diff --git a/plugins/torrenttransfer/__init__.py b/plugins/torrenttransfer/__init__.py index cdfbcc1..03dc21e 100644 --- a/plugins/torrenttransfer/__init__.py +++ b/plugins/torrenttransfer/__init__.py @@ -27,7 +27,7 @@ class TorrentTransfer(_PluginBase): # 插件图标 plugin_icon = "seed.png" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -102,16 +102,14 @@ class TorrentTransfer(_PluginBase): logger.error(f"源下载器和目的下载器不能相同") self.systemmessage.put(f"源下载器和目的下载器不能相同") return + + # 定时服务 self._scheduler = BackgroundScheduler(timezone=settings.TZ) - if self._cron: - logger.info(f"转移做种服务启动,周期:{self._cron}") - try: - self._scheduler.add_job(self.transfer, - CronTrigger.from_crontab(self._cron)) - except Exception as e: - logger.error(f"转移做种服务启动失败:{str(e)}") - self.systemmessage.put(f"转移做种服务启动失败:{str(e)}") - return + + if self._autostart: + # 追加种子校验服务 + self._scheduler.add_job(self.check_recheck, 'interval', minutes=3) + if self._onlyonce: logger.info(f"转移做种服务启动,立即运行一次") self._scheduler.add_job(self.transfer, 'date', @@ -135,11 +133,9 @@ class TorrentTransfer(_PluginBase): "nopaths": self._nopaths, "autostart": self._autostart }) + + # 启动服务 if self._scheduler.get_jobs(): - if self._autostart: - # 追加种子校验服务 - self._scheduler.add_job(self.check_recheck, 'interval', minutes=3) - # 启动服务 self._scheduler.print_jobs() self._scheduler.start() @@ -157,6 +153,29 @@ class TorrentTransfer(_PluginBase): def get_api(self) -> List[Dict[str, Any]]: pass + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + [{ + "id": "服务ID", + "name": "服务名称", + "trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()", + "func": self.xxx, + "kwargs": {} # 定时器参数 + }] + """ + if self.get_state(): + return [ + { + "id": "TorrentTransfer", + "name": "转移做种服务", + "trigger": CronTrigger.from_crontab(self._cron), + "func": self.transfer, + "kwargs": {} + } + ] + return [] + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 diff --git a/plugins/workwechatmsg/__init__.py b/plugins/workwechatmsg/__init__.py index 1993080..f5ddc7d 100644 --- a/plugins/workwechatmsg/__init__.py +++ b/plugins/workwechatmsg/__init__.py @@ -1,11 +1,10 @@ -from urllib.parse import urlencode +from typing import Any, List, Dict, Tuple -from app.plugins import _PluginBase from app.core.event import eventmanager, Event +from app.log import logger +from app.plugins import _PluginBase from app.schemas.types import EventType, NotificationType from app.utils.http import RequestUtils -from typing import Any, List, Dict, Tuple -from app.log import logger class WorkWechatMsg(_PluginBase): @@ -186,18 +185,18 @@ class WorkWechatMsg(_PluginBase): payload = { "msgtype": "news", "news": { - "articles" : [ + "articles": [ { - "title" : title, - "description" : text, - "url" : "moviepilot", - "picurl" : image + "title": title, + "description": text, + "url": "moviepilot", + "picurl": image } ] } } - res = RequestUtils().post_res(url = self._webhookurl, json = payload) + res = RequestUtils().post_res(url=self._webhookurl, json=payload) if res and res.status_code == 200: ret_json = res.json() errno = ret_json.get('errcode')