Merge branch 'main' into main

This commit is contained in:
jxxghp 2023-08-03 18:33:07 +08:00 committed by GitHub
commit 1d745fe9de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 554 additions and 131 deletions

View File

@ -185,6 +185,12 @@ class PluginManager(metaclass=Singleton):
conf.update({"installed": True}) conf.update({"installed": True})
else: else:
conf.update({"installed": False}) conf.update({"installed": False})
# 运行状态
if pid in self._running_plugins.keys() and hasattr(plugin, "get_state"):
plugin_obj = self._running_plugins.get(pid)
conf.update({"state": plugin_obj.get_state()})
else:
conf.update({"state": False})
# 名称 # 名称
if hasattr(plugin, "plugin_name"): if hasattr(plugin, "plugin_name"):
conf.update({"plugin_name": plugin.plugin_name}) conf.update({"plugin_name": plugin.plugin_name})

View File

@ -51,14 +51,16 @@ class FileTransferModule(_ModuleBase):
if isinstance(result, str): if isinstance(result, str):
return TransferInfo(message=result) return TransferInfo(message=result)
# 解包结果 # 解包结果
target_path, file_count, file_size, fail_list, msg = result is_bluray, target_path, file_list, file_size, fail_list, msg = result
# 返回 # 返回
return TransferInfo(path=path, return TransferInfo(path=path,
target_path=target_path, target_path=target_path,
message=msg, message=msg,
file_count=file_count, file_count=len(file_list),
total_size=file_size, total_size=file_size,
fail_list=fail_list) fail_list=fail_list,
is_bluray=is_bluray,
file_list=file_list)
@staticmethod @staticmethod
def __transfer_command(file_item: Path, target_file: Path, rmt_mode) -> int: def __transfer_command(file_item: Path, target_file: Path, rmt_mode) -> int:
@ -333,14 +335,14 @@ class FileTransferModule(_ModuleBase):
mediainfo: MediaInfo, mediainfo: MediaInfo,
rmt_mode: str = None, rmt_mode: str = None,
target_dir: Path = None target_dir: Path = None
) -> Union[str, Tuple[Path, int, int, List[Path], str]]: ) -> Union[str, Tuple[bool, Path, list, int, List[Path], str]]:
""" """
识别并转移一个文件多个文件或者目录 识别并转移一个文件多个文件或者目录
:param in_path: 转移的路径可能是一个文件也可以是一个目录 :param in_path: 转移的路径可能是一个文件也可以是一个目录
:param target_dir: 目的文件夹非空的转移到该文件夹为空时则按类型转移到配置文件中的媒体库文件夹 :param target_dir: 目的文件夹非空的转移到该文件夹为空时则按类型转移到配置文件中的媒体库文件夹
:param rmt_mode: 文件转移方式 :param rmt_mode: 文件转移方式
:param mediainfo: 媒体信息 :param mediainfo: 媒体信息
:return: 目的路径处理文件数总大小失败文件列表错误信息 :return: 是否蓝光原盘目的路径处理文件清单总大小失败文件列表错误信息
""" """
# 检查目录路径 # 检查目录路径
if not in_path.exists(): if not in_path.exists():
@ -370,8 +372,8 @@ class FileTransferModule(_ModuleBase):
# 总大小 # 总大小
total_filesize = 0 total_filesize = 0
# 处理文件 # 处理文件清单
total_num = 0 file_list = []
# 失败文件清单 # 失败文件清单
fail_list = [] fail_list = []
@ -398,12 +400,10 @@ class FileTransferModule(_ModuleBase):
if retcode != 0: if retcode != 0:
return f"{retcode},蓝光原盘转移失败" return f"{retcode},蓝光原盘转移失败"
else: else:
# 计算文件数
total_num += 1
# 计算大小 # 计算大小
total_filesize += in_path.stat().st_size total_filesize += in_path.stat().st_size
# 返回转移后的路径 # 返回转移后的路径
return new_path, total_num, total_filesize, [], "" return bluray_flag, new_path, [], total_filesize, [], ""
else: else:
# 获取文件清单 # 获取文件清单
transfer_files: List[Path] = SystemUtils.list_files_with_extensions(in_path, settings.RMT_MEDIAEXT) transfer_files: List[Path] = SystemUtils.list_files_with_extensions(in_path, settings.RMT_MEDIAEXT)
@ -466,7 +466,7 @@ class FileTransferModule(_ModuleBase):
fail_list.append(transfer_file) fail_list.append(transfer_file)
continue continue
# 计算文件数 # 计算文件数
total_num += 1 file_list.append(str(new_file))
# 计算大小 # 计算大小
total_filesize += new_file.stat().st_size total_filesize += new_file.stat().st_size
except Exception as err: except Exception as err:
@ -474,12 +474,12 @@ class FileTransferModule(_ModuleBase):
logger.error(f"{transfer_file}转移失败:{err}") logger.error(f"{transfer_file}转移失败:{err}")
fail_list.append(transfer_file) fail_list.append(transfer_file)
if total_num == 0: if not file_list:
# 没有成功的 # 没有成功的
return "\n".join(err_msgs) return "\n".join(err_msgs)
# 新路径、处理文件数、总大小、失败文件列表、错误信息 # 蓝光原盘、新路径、处理文件清单、总大小、失败文件列表、错误信息
return new_path, total_num, total_filesize, fail_list, "\n".join(err_msgs) return bluray_flag, new_path, file_list, total_filesize, fail_list, "\n".join(err_msgs)
@staticmethod @staticmethod
def __get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: str = None) -> dict: def __get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: str = None) -> dict:

View File

@ -95,6 +95,13 @@ class _PluginBase(metaclass=ABCMeta):
""" """
pass pass
@abstractmethod
def get_state(self) -> bool:
"""
获取插件运行状态
"""
pass
@abstractmethod @abstractmethod
def stop_service(self): def stop_service(self):
""" """

View File

@ -113,6 +113,9 @@ class AutoSignIn(_PluginBase):
self._scheduler.print_jobs() self._scheduler.print_jobs()
self._scheduler.start() self._scheduler.start()
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
""" """

View File

@ -3,9 +3,11 @@ from pathlib import Path
from typing import List, Tuple, Dict, Any from typing import List, Tuple, Dict, Any
from app.core.config import settings from app.core.config import settings
from app.core.context import MediaInfo
from app.core.event import eventmanager from app.core.event import eventmanager
from app.log import logger from app.log import logger
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.schemas import TransferInfo
from app.schemas.types import EventType, MediaType from app.schemas.types import EventType, MediaType
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
@ -14,7 +16,7 @@ class ChineseSubFinder(_PluginBase):
# 插件名称 # 插件名称
plugin_name = "ChineseSubFinder" plugin_name = "ChineseSubFinder"
# 插件描述 # 插件描述
plugin_desc = "通知ChineseSubFinder下载字幕。" plugin_desc = "整理入库时通知ChineseSubFinder下载字幕。"
# 插件图标 # 插件图标
plugin_icon = "chinesesubfinder.png" plugin_icon = "chinesesubfinder.png"
# 主题色 # 主题色
@ -34,20 +36,16 @@ class ChineseSubFinder(_PluginBase):
# 私有属性 # 私有属性
_save_tmp_path = None _save_tmp_path = None
_enable = False _enabled = False
_host = None _host = None
_api_key = None _api_key = None
_remote_path = None _remote_path = None
_local_path = None _local_path = None
_remote_path2 = None
_local_path2 = None
_remote_path3 = None
_local_path3 = None
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
self._save_tmp_path = settings.TEMP_PATH self._save_tmp_path = settings.TEMP_PATH
if config: if config:
self._enable = config.get("enable") self._enabled = config.get("enabled")
self._api_key = config.get("api_key") self._api_key = config.get("api_key")
self._host = config.get('host') self._host = config.get('host')
if self._host: if self._host:
@ -57,10 +55,6 @@ class ChineseSubFinder(_PluginBase):
self._host = self._host + "/" self._host = self._host + "/"
self._local_path = config.get("local_path") self._local_path = config.get("local_path")
self._remote_path = config.get("remote_path") self._remote_path = config.get("remote_path")
self._local_path2 = config.get("local_path2")
self._remote_path2 = config.get("remote_path2")
self._local_path3 = config.get("local_path3")
self._remote_path3 = config.get("remote_path3")
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
@ -70,7 +64,117 @@ class ChineseSubFinder(_PluginBase):
pass pass
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
pass return [
{
'component': 'VForm',
'content': [
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'enabled',
'label': '启用插件',
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'host',
'label': '服务器'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'api_key',
'label': 'API密钥'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'local_path',
'label': '本地路径'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'remote_path',
'label': '远端路径'
}
}
]
}
]
}
]
}
], {
"enabled": False,
"host": "",
"api_key": "",
"local_path": "",
"remote_path": ""
}
def get_state(self) -> bool:
return self._enabled
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
pass pass
@ -88,33 +192,31 @@ class ChineseSubFinder(_PluginBase):
item = event.event_data item = event.event_data
if not item: if not item:
return return
# FIXME # 请求地址
req_url = "%sapi/v1/add-job" % self._host req_url = "%sapi/v1/add-job" % self._host
item_media = item.get("media_info") # 媒体信息
item_type = item_media.get("type") item_media: MediaInfo = item.get("mediainfo")
item_bluray = item.get("bluray") # 转移信息
item_file = item.get("file") item_transfer: TransferInfo = item.get("transferinfo")
item_file_ext = item.get("file_ext") # 类型
item_type = item_media.type
# 目的路径
item_dest: Path = item_transfer.target_path
# 是否蓝光原盘
item_bluray = item_transfer.is_bluray
# 文件清单
item_file_list = item_transfer.file_list
if item_bluray: if item_bluray:
file_path = "%s.mp4" % item_file # 蓝光原盘虚拟个文件
else: item_file_list = ["%s.mp4" % item_dest / item_dest.name]
if Path(item_file).suffix != item_file_ext:
file_path = "%s%s" % (item_file, item_file_ext)
else:
file_path = item_file
for file_path in item_file_list:
# 路径替换 # 路径替换
if self._local_path and self._remote_path and file_path.startswith(self._local_path): if self._local_path and self._remote_path and file_path.startswith(self._local_path):
file_path = file_path.replace(self._local_path, self._remote_path).replace('\\', '/') file_path = file_path.replace(self._local_path, self._remote_path).replace('\\', '/')
if self._local_path2 and self._remote_path2 and file_path.startswith(self._local_path2):
file_path = file_path.replace(self._local_path2, self._remote_path2).replace('\\', '/')
if self._local_path3 and self._remote_path3 and file_path.startswith(self._local_path3):
file_path = file_path.replace(self._local_path3, self._remote_path3).replace('\\', '/')
# 调用CSF下载字幕 # 调用CSF下载字幕
self.__request_csf(req_url=req_url, self.__request_csf(req_url=req_url,
file_path=file_path, file_path=file_path,

View File

@ -1,12 +1,11 @@
from typing import List, Tuple, Dict, Any from typing import List, Tuple, Dict, Any
from python_hosts import Hosts, HostsEntry
from app.log import logger from app.log import logger
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.utils.ip import IpUtils from app.utils.ip import IpUtils
from app.utils.system import SystemUtils from app.utils.system import SystemUtils
from python_hosts import Hosts, HostsEntry
class CustomHosts(_PluginBase): class CustomHosts(_PluginBase):
# 插件名称 # 插件名称
@ -60,6 +59,9 @@ class CustomHosts(_PluginBase):
"enabled": self._enabled "enabled": self._enabled
}) })
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
pass pass
@ -101,6 +103,9 @@ class CustomHosts(_PluginBase):
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': {
'cols': 12
},
'content': [ 'content': [
{ {
'component': 'VTextarea', 'component': 'VTextarea',
@ -120,6 +125,9 @@ class CustomHosts(_PluginBase):
'content': [ 'content': [
{ {
'component': 'VCol', 'component': 'VCol',
'props': {
'cols': 12
},
'content': [ 'content': [
{ {
'component': 'VTextarea', 'component': 'VTextarea',
@ -140,7 +148,7 @@ class CustomHosts(_PluginBase):
], { ], {
"enabled": False, "enabled": False,
"hosts": "", "hosts": "",
"err_hosts": "", "err_hosts": ""
} }
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
@ -189,6 +197,8 @@ class CustomHosts(_PluginBase):
except Exception as err: except Exception as err:
err_hosts.append(host + "\n") err_hosts.append(host + "\n")
logger.error(f"{host} 格式转换错误:{str(err)}") logger.error(f"{host} 格式转换错误:{str(err)}")
# 推送实时消息
self.systemmessage.put(f"{host} 格式转换错误:{str(err)}")
# 写入系统hosts # 写入系统hosts
if new_entrys: if new_entrys:
@ -202,11 +212,10 @@ class CustomHosts(_PluginBase):
except Exception as err: except Exception as err:
err_flag = True err_flag = True
logger.error(f"更新系统hosts文件失败{str(err) or '请检查权限'}") logger.error(f"更新系统hosts文件失败{str(err) or '请检查权限'}")
# 推送实时消息
self.systemmessage.put(f"更新系统hosts文件失败{str(err) or '请检查权限'}")
return err_flag, err_hosts return err_flag, err_hosts
def get_state(self):
return self._enable and self._hosts and self._hosts[0]
def stop_service(self): def stop_service(self):
""" """
退出插件 退出插件

View File

@ -27,18 +27,21 @@ class DirMonitor(_PluginBase):
# 私有属性 # 私有属性
_monitor = None _monitor = None
_enable = False _enabled = False
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
# 读取配置 # 读取配置
if config: if config:
self._enable = config.get("enable") self._enabled = config.get("enabled")
# 停止现有任务 # 停止现有任务
self.stop_service() self.stop_service()
# TODO 启动任务 # TODO 启动任务
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
pass pass

View File

@ -1,12 +1,17 @@
import datetime
import re import re
import xml.dom.minidom import xml.dom.minidom
from threading import Event from threading import Event
from typing import Tuple, List, Dict, Any from typing import Tuple, List, Dict, Any, Optional
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
from app.chain.download import DownloadChain
from app.chain.subscribe import SubscribeChain
from app.core.config import settings from app.core.config import settings
from app.core.context import MediaInfo
from app.core.metainfo import MetaInfo
from app.log import logger from app.log import logger
from app.plugins import _PluginBase from app.plugins import _PluginBase
from app.utils.dom import DomUtils from app.utils.dom import DomUtils
@ -14,7 +19,6 @@ from app.utils.http import RequestUtils
class DoubanRank(_PluginBase): class DoubanRank(_PluginBase):
# 插件名称 # 插件名称
plugin_name = "豆瓣榜单订阅" plugin_name = "豆瓣榜单订阅"
# 插件描述 # 插件描述
@ -39,10 +43,9 @@ class DoubanRank(_PluginBase):
# 退出事件 # 退出事件
_event = Event() _event = Event()
# 私有属性 # 私有属性
mediaserver = None downloadchain: DownloadChain = None
subscribe = None subscribechain: SubscribeChain = None
rsshelper = None _scheduler = None
media = None
_douban_address = { _douban_address = {
'movie-ustop': 'https://rsshub.app/douban/movie/ustop', 'movie-ustop': 'https://rsshub.app/douban/movie/ustop',
'movie-weekly': 'https://rsshub.app/douban/movie/weekly', 'movie-weekly': 'https://rsshub.app/douban/movie/weekly',
@ -52,16 +55,18 @@ class DoubanRank(_PluginBase):
'tv-hot': 'https://rsshub.app/douban/movie/weekly/tv_hot', 'tv-hot': 'https://rsshub.app/douban/movie/weekly/tv_hot',
'movie-top250': 'https://rsshub.app/douban/movie/weekly/movie_top250', 'movie-top250': 'https://rsshub.app/douban/movie/weekly/movie_top250',
} }
_enable = False _enabled = False
_cron = "" _cron = ""
_rss_addrs = [] _rss_addrs = []
_ranks = [] _ranks = []
_vote = 0 _vote = 0
_scheduler = None
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
self.downloadchain = DownloadChain()
self.subscribechain = SubscribeChain()
if config: if config:
self._enable = config.get("enable") self._enabled = config.get("enabled")
self._cron = config.get("cron") self._cron = config.get("cron")
self._vote = float(config.get("vote")) if config.get("vote") else 0 self._vote = float(config.get("vote")) if config.get("vote") else 0
rss_addrs = config.get("rss_addrs") rss_addrs = config.get("rss_addrs")
@ -78,18 +83,28 @@ class DoubanRank(_PluginBase):
self.stop_service() self.stop_service()
# 启动服务 # 启动服务
if self._enable: if self._enabled:
self._scheduler = BackgroundScheduler(timezone=settings.TZ) self._scheduler = BackgroundScheduler(timezone=settings.TZ)
if self._cron: if self._cron:
logger.info(f"豆瓣榜单订阅服务启动,周期:{self._cron}") logger.info(f"豆瓣榜单订阅服务启动,周期:{self._cron}")
try:
self._scheduler.add_job(self.__refresh_rss, self._scheduler.add_job(self.__refresh_rss,
CronTrigger.from_crontab(self._cron)) CronTrigger.from_crontab(self._cron))
except Exception as e:
logger.error(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}")
self.systemmessage.put(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}")
else:
self._scheduler.add_job(self.__refresh_rss, CronTrigger.from_crontab("0 8 * * *"))
logger.info("豆瓣榜单订阅服务启动,周期:每天 08:00")
if self._scheduler.get_jobs(): if self._scheduler.get_jobs():
# 启动服务 # 启动服务
self._scheduler.print_jobs() self._scheduler.print_jobs()
self._scheduler.start() self._scheduler.start()
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
pass pass
@ -98,13 +113,227 @@ class DoubanRank(_PluginBase):
pass pass
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
pass return [
{
'component': 'VForm',
'content': [
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'enabled',
'label': '启用插件',
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'cron',
'label': '执行周期',
'placeholder': '5位cron表达式留空自动'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VTextField',
'props': {
'model': 'vote',
'label': '评分',
'placeholder': '评分大于等于该值才订阅'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'content': [
{
'component': 'VSelect',
'props': {
'chips': True,
'multiple': True,
'model': 'ranks',
'label': '热门榜单',
'items': [
{'title': '电影北美票房榜', 'value': 'movie-ustop'},
{'title': '一周口碑电影榜', 'value': 'movie-weekly'},
{'title': '实时热门电影', 'value': 'movie-real-time'},
{'title': '热门综艺', 'value': 'show-domestic'},
{'title': '热门电影', 'value': 'movie-hot-gaia'},
{'title': '热门电视剧', 'value': 'tv-hot'},
{'title': '电影TOP10', 'value': 'movie-top250'},
]
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'rss_addrs',
'label': '自定义榜单地址',
'placeholder': '每行一个地址https://rsshub.app/douban/movie/ustop'
}
}
]
}
]
}
]
}
], {
"enabled": False,
"cron": "",
"vote": "",
"ranks": [],
"rss_addrs": "",
}
def get_page(self) -> List[dict]: def get_page(self) -> List[dict]:
""" """
拼装插件详情页面需要返回页面配置同时附带数据 拼装插件详情页面需要返回页面配置同时附带数据
""" """
pass # 查询历史记录
historys = self.get_data('history')
if not historys:
return [
{
'component': 'div',
'text': '暂无数据',
'props': {
'class': 'text-center',
}
}
]
# 数据按时间降序排序
historys = sorted(historys, key=lambda x: x.get('time'), reverse=True)
# 拼装页面
contents = []
for history in historys:
title = history.get("title")
poster = history.get("poster")
mtype = history.get("type")
time_str = history.get("time")
doubanid = history.get("doubanid")
contents.append(
{
'component': 'VCard',
'content': [
{
'component': 'div',
'props': {
'class': 'd-flex justify-space-start flex-nowrap flex-row',
},
'content': [
{
'component': 'div',
'content': [
{
'component': 'VImg',
'props': {
'src': poster,
'height': 120,
'width': 80,
'aspect-ratio': '2/3',
'class': 'object-cover shadow ring-gray-500',
'cover': True
}
}
]
},
{
'component': 'div',
'content': [
{
'component': 'VCardSubtitle',
'props': {
'class': 'pa-2 font-bold break-words whitespace-break-spaces'
},
'content': [
{
'component': 'a',
'props': {
'href': f"https://movie.douban.com/subject/{doubanid}",
'target': '_blank'
},
'text': title
}
]
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'类型:{mtype}'
},
{
'component': 'VCardText',
'props': {
'class': 'pa-0 px-2'
},
'text': f'时间:{time_str}'
}
]
}
]
}
]
}
)
return [
{
'component': 'div',
'props': {
'class': 'grid gap-3 grid-info-card',
},
'content': contents
}
]
def stop_service(self): def stop_service(self):
""" """
@ -125,13 +354,17 @@ class DoubanRank(_PluginBase):
""" """
刷新RSS 刷新RSS
""" """
logger.info(f"开始刷新RSS ...") logger.info(f"开始刷新豆瓣榜单 ...")
addr_list = self._rss_addrs + [self._douban_address.get(rank) for rank in self._ranks] addr_list = self._rss_addrs + [self._douban_address.get(rank) for rank in self._ranks]
if not addr_list: if not addr_list:
logger.info(f"未设置RSS地址") logger.info(f"未设置榜单RSS地址")
return return
else: else:
logger.info(f"{len(addr_list)} 个RSS地址需要刷新") logger.info(f"{len(addr_list)} 个榜单RSS地址需要刷新")
# 读取历史记录
history: List[dict] = self.get_data('history') or []
for addr in addr_list: for addr in addr_list:
if not addr: if not addr:
continue continue
@ -150,18 +383,64 @@ class DoubanRank(_PluginBase):
title = rss_info.get('title') title = rss_info.get('title')
douban_id = rss_info.get('doubanid') douban_id = rss_info.get('doubanid')
mtype = rss_info.get('type')
unique_flag = f"doubanrank: {title} (DB:{douban_id})" unique_flag = f"doubanrank: {title} (DB:{douban_id})"
# TODO 检查是否已处理过 # 检查是否已处理过
# TODO 识别媒体信息 if unique_flag in [h.get("unique") for h in history]:
# TODO 检查媒体服务器是否存在 continue
# TODO 检查是否已订阅过 # 识别媒体信息
# TODO 添加处理历史 if douban_id:
# TODO 添加订阅 # 根据豆瓣ID获取豆瓣数据
# TODO 发送通知 doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id)
# TODO 更新历史记录 if not doubaninfo:
logger.warn(f'未获取到豆瓣信息,标题:{title}豆瓣ID{douban_id}')
continue
logger.info(f'获取到豆瓣信息,标题:{title}豆瓣ID{douban_id}')
# 识别
title = doubaninfo.get("title")
meta = MetaInfo(doubaninfo.get("original_title") or title)
if doubaninfo.get("year"):
meta.year = doubaninfo.get("year")
else:
meta = MetaInfo(title)
# 匹配媒体信息
mediainfo: MediaInfo = self.chain.recognize_media(meta=meta)
if not mediainfo:
logger.warn(f'未识别到媒体信息,标题:{title}豆瓣ID{douban_id}')
continue
# 查询缺失的媒体信息
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
if exist_flag:
logger.info(f'{mediainfo.title_year} 媒体库中已存在')
action = "exist"
else:
# 添加订阅
self.subscribechain.add(title=mediainfo.title,
year=mediainfo.year,
mtype=mediainfo.type,
tmdbid=mediainfo.tmdb_id,
season=meta.begin_season,
exist_ok=True,
username="豆瓣榜单")
action = "subscribe"
# 存储历史记录
history.append({
"action": action,
"title": title,
"type": mediainfo.type.value,
"year": mediainfo.year,
"poster": mediainfo.get_poster_image(),
"overview": mediainfo.overview,
"tmdbid": mediainfo.tmdb_id,
"doubanid": douban_id,
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"unique": unique_flag
})
except Exception as e: except Exception as e:
logger.error(str(e)) logger.error(str(e))
# 保存历史记录
self.save_data('history', history)
logger.info(f"所有榜单RSS刷新完成") logger.info(f"所有榜单RSS刷新完成")
@staticmethod @staticmethod

View File

@ -61,7 +61,6 @@ class DoubanSync(_PluginBase):
_users: str = "" _users: str = ""
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
self._cache_path = settings.TEMP_PATH / "__doubansync_cache__"
self.rsshelper = RssHelper() self.rsshelper = RssHelper()
self.downloadchain = DownloadChain() self.downloadchain = DownloadChain()
self.searchchain = SearchChain() self.searchchain = SearchChain()
@ -98,6 +97,9 @@ class DoubanSync(_PluginBase):
self._scheduler.print_jobs() self._scheduler.print_jobs()
self._scheduler.start() self._scheduler.start()
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
""" """
@ -259,7 +261,6 @@ class DoubanSync(_PluginBase):
poster = history.get("poster") poster = history.get("poster")
mtype = history.get("type") mtype = history.get("type")
time_str = history.get("time") time_str = history.get("time")
overview = history.get("overview")
doubanid = history.get("doubanid") doubanid = history.get("doubanid")
contents.append( contents.append(
{ {
@ -357,10 +358,8 @@ class DoubanSync(_PluginBase):
""" """
if not self._users: if not self._users:
return return
# 读取缓存
caches = self._cache_path.read_text().split("\n") if self._cache_path.exists() else []
# 读取历史记录 # 读取历史记录
history = self.get_data('history') or [] history: List[dict] = self.get_data('history') or []
for user_id in self._users.split(","): for user_id in self._users.split(","):
# 同步每个用户的豆瓣数据 # 同步每个用户的豆瓣数据
if not user_id: if not user_id:
@ -387,8 +386,8 @@ class DoubanSync(_PluginBase):
logger.info(f'已超过同步天数,标题:{title},发布时间:{pubdate}') logger.info(f'已超过同步天数,标题:{title},发布时间:{pubdate}')
continue continue
douban_id = result.get("link", "").split("/")[-2] douban_id = result.get("link", "").split("/")[-2]
# 检查缓存 # 检查是否处理过
if not douban_id or douban_id in caches: if not douban_id or douban_id in [h.get("doubanid") for h in history]:
continue continue
# 根据豆瓣ID获取豆瓣数据 # 根据豆瓣ID获取豆瓣数据
doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id) doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id)
@ -404,8 +403,6 @@ class DoubanSync(_PluginBase):
if not mediainfo: if not mediainfo:
logger.warn(f'未识别到媒体信息,标题:{title}豆瓣ID{douban_id}') logger.warn(f'未识别到媒体信息,标题:{title}豆瓣ID{douban_id}')
continue continue
# 加入缓存
caches.append(douban_id)
# 查询缺失的媒体信息 # 查询缺失的媒体信息
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
if exist_flag: if exist_flag:
@ -447,7 +444,6 @@ class DoubanSync(_PluginBase):
username="豆瓣想看") username="豆瓣想看")
action = "subscribe" action = "subscribe"
# 存储历史记录 # 存储历史记录
if douban_id not in [h.get("doubanid") for h in history]:
history.append({ history.append({
"action": action, "action": action,
"title": doubaninfo.get("title") or mediainfo.title, "title": doubaninfo.get("title") or mediainfo.title,
@ -464,8 +460,6 @@ class DoubanSync(_PluginBase):
logger.info(f"用户 {user_id} 豆瓣想看同步完成") logger.info(f"用户 {user_id} 豆瓣想看同步完成")
# 保存历史记录 # 保存历史记录
self.save_data('history', history) self.save_data('history', history)
# 保存缓存
self._cache_path.write_text("\n".join(caches))
@eventmanager.register(EventType.DoubanSync) @eventmanager.register(EventType.DoubanSync)
def remote_sync(self, event: Event): def remote_sync(self, event: Event):

View File

@ -36,7 +36,7 @@ class LibraryScraper(_PluginBase):
_scheduler = None _scheduler = None
_scraper = None _scraper = None
# 限速开关 # 限速开关
_enable = False _enabled = False
_cron = None _cron = None
_mode = None _mode = None
_scraper_path = None _scraper_path = None
@ -47,7 +47,7 @@ class LibraryScraper(_PluginBase):
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
# 读取配置 # 读取配置
if config: if config:
self._enable = config.get("enable") self._enabled = config.get("enabled")
self._cron = config.get("cron") self._cron = config.get("cron")
self._mode = config.get("mode") self._mode = config.get("mode")
self._scraper_path = config.get("scraper_path") self._scraper_path = config.get("scraper_path")
@ -57,7 +57,7 @@ class LibraryScraper(_PluginBase):
self.stop_service() self.stop_service()
# 启动定时任务 & 立即运行一次 # 启动定时任务 & 立即运行一次
if self._enable: if self._enabled:
self._scheduler = BackgroundScheduler(timezone=settings.TZ) self._scheduler = BackgroundScheduler(timezone=settings.TZ)
if self._cron: if self._cron:
logger.info(f"媒体库刮削服务启动,周期:{self._cron}") logger.info(f"媒体库刮削服务启动,周期:{self._cron}")
@ -68,6 +68,9 @@ class LibraryScraper(_PluginBase):
self._scheduler.print_jobs() self._scheduler.print_jobs()
self._scheduler.start() self._scheduler.start()
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
pass pass

View File

@ -114,6 +114,9 @@ class SiteStatistic(_PluginBase):
self._scheduler.print_jobs() self._scheduler.print_jobs()
self._scheduler.start() self._scheduler.start()
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
""" """

View File

@ -31,7 +31,7 @@ class SpeedLimiter(_PluginBase):
# 私有属性 # 私有属性
_scheduler = None _scheduler = None
_enable: bool = False _enabled: bool = False
_notify: bool = False _notify: bool = False
_bandwidth: int = 0 _bandwidth: int = 0
_interval: int = 60 _interval: int = 60
@ -39,7 +39,7 @@ class SpeedLimiter(_PluginBase):
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
# 读取配置 # 读取配置
if config: if config:
self._enable = config.get("enable") self._enabled = config.get("enabled")
self._notify = config.get("notify") self._notify = config.get("notify")
try: try:
# 总带宽 # 总带宽
@ -52,7 +52,7 @@ class SpeedLimiter(_PluginBase):
self.stop_service() self.stop_service()
# 启动限速任务 # 启动限速任务
if self._enable: if self._enabled:
self._scheduler = BackgroundScheduler(timezone=settings.TZ) self._scheduler = BackgroundScheduler(timezone=settings.TZ)
self._scheduler.add_job(func=self.__check_playing_sessions, self._scheduler.add_job(func=self.__check_playing_sessions,
trigger='interval', trigger='interval',
@ -61,6 +61,9 @@ class SpeedLimiter(_PluginBase):
self._scheduler.start() self._scheduler.start()
logger.info("播放限速服务启动") logger.info("播放限速服务启动")
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
pass pass

View File

@ -29,11 +29,14 @@ class TorrentRemover(_PluginBase):
# 私有属性 # 私有属性
downloader = None downloader = None
_enable = False _enabled = False
def init_plugin(self, config: dict = None): def init_plugin(self, config: dict = None):
if config: if config:
self._enable = config.get("enable") self._enabled = config.get("enabled")
def get_state(self) -> bool:
return self._enabled
@staticmethod @staticmethod
def get_command() -> List[Dict[str, Any]]: def get_command() -> List[Dict[str, Any]]:
@ -56,7 +59,7 @@ class TorrentRemover(_PluginBase):
""" """
联动删除下载器中的下载任务 联动删除下载器中的下载任务
""" """
if not self._enable: if not self._enabled:
return return
event_info = event.event_data event_info = event.event_data
if not event_info: if not event_info:

View File

@ -1,3 +1,5 @@
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
@ -7,24 +9,26 @@ class Plugin(BaseModel):
""" """
id: str = None id: str = None
# 插件名称 # 插件名称
plugin_name: str = None plugin_name: Optional[str] = None
# 插件描述 # 插件描述
plugin_desc: str = None plugin_desc: Optional[str] = None
# 插件图标 # 插件图标
plugin_icon: str = None plugin_icon: Optional[str] = None
# 主题色 # 主题色
plugin_color: str = None plugin_color: Optional[str] = None
# 插件版本 # 插件版本
plugin_version: str = None plugin_version: Optional[str] = None
# 插件作者 # 插件作者
plugin_author: str = None plugin_author: Optional[str] = None
# 作者主页 # 作者主页
author_url: str = None author_url: Optional[str] = None
# 插件配置项ID前缀 # 插件配置项ID前缀
plugin_config_prefix: str = None plugin_config_prefix: Optional[str] = None
# 加载顺序 # 加载顺序
plugin_order: int = 0 plugin_order: Optional[int] = 0
# 可使用的用户级别 # 可使用的用户级别
auth_level: int = 0 auth_level: Optional[int] = 0
# 是否已安装 # 是否已安装
installed: bool = False installed: Optional[bool] = False
# 运行状态
state: Optional[bool] = False

View File

@ -39,8 +39,12 @@ class TransferInfo(BaseModel):
path: Optional[Path] = None path: Optional[Path] = None
# 转移后路径 # 转移后路径
target_path: Optional[Path] = None target_path: Optional[Path] = None
# 是否蓝光原盘
is_bluray: Optional[bool] = False
# 处理文件数 # 处理文件数
file_count: Optional[int] = 0 file_count: Optional[int] = 0
# 处理文件清单
file_list: Optional[list] = []
# 总文件大小 # 总文件大小
total_size: Optional[float] = 0 total_size: Optional[float] = 0
# 失败清单 # 失败清单