Merge branch 'main' into main
This commit is contained in:
commit
1d745fe9de
@ -185,6 +185,12 @@ class PluginManager(metaclass=Singleton):
|
||||
conf.update({"installed": True})
|
||||
else:
|
||||
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"):
|
||||
conf.update({"plugin_name": plugin.plugin_name})
|
||||
|
@ -51,14 +51,16 @@ class FileTransferModule(_ModuleBase):
|
||||
if isinstance(result, str):
|
||||
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,
|
||||
target_path=target_path,
|
||||
message=msg,
|
||||
file_count=file_count,
|
||||
file_count=len(file_list),
|
||||
total_size=file_size,
|
||||
fail_list=fail_list)
|
||||
fail_list=fail_list,
|
||||
is_bluray=is_bluray,
|
||||
file_list=file_list)
|
||||
|
||||
@staticmethod
|
||||
def __transfer_command(file_item: Path, target_file: Path, rmt_mode) -> int:
|
||||
@ -333,14 +335,14 @@ class FileTransferModule(_ModuleBase):
|
||||
mediainfo: MediaInfo,
|
||||
rmt_mode: str = 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 target_dir: 目的文件夹,非空的转移到该文件夹,为空时则按类型转移到配置文件中的媒体库文件夹
|
||||
:param rmt_mode: 文件转移方式
|
||||
:param mediainfo: 媒体信息
|
||||
:return: 目的路径、处理文件数、总大小、失败文件列表、错误信息
|
||||
:return: 是否蓝光原盘、目的路径、处理文件清单、总大小、失败文件列表、错误信息
|
||||
"""
|
||||
# 检查目录路径
|
||||
if not in_path.exists():
|
||||
@ -370,8 +372,8 @@ class FileTransferModule(_ModuleBase):
|
||||
# 总大小
|
||||
total_filesize = 0
|
||||
|
||||
# 处理文件数
|
||||
total_num = 0
|
||||
# 处理文件清单
|
||||
file_list = []
|
||||
|
||||
# 失败文件清单
|
||||
fail_list = []
|
||||
@ -398,12 +400,10 @@ class FileTransferModule(_ModuleBase):
|
||||
if retcode != 0:
|
||||
return f"{retcode},蓝光原盘转移失败"
|
||||
else:
|
||||
# 计算文件数
|
||||
total_num += 1
|
||||
# 计算大小
|
||||
total_filesize += in_path.stat().st_size
|
||||
# 返回转移后的路径
|
||||
return new_path, total_num, total_filesize, [], ""
|
||||
return bluray_flag, new_path, [], total_filesize, [], ""
|
||||
else:
|
||||
# 获取文件清单
|
||||
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)
|
||||
continue
|
||||
# 计算文件数
|
||||
total_num += 1
|
||||
file_list.append(str(new_file))
|
||||
# 计算大小
|
||||
total_filesize += new_file.stat().st_size
|
||||
except Exception as err:
|
||||
@ -474,12 +474,12 @@ class FileTransferModule(_ModuleBase):
|
||||
logger.error(f"{transfer_file}转移失败:{err}")
|
||||
fail_list.append(transfer_file)
|
||||
|
||||
if total_num == 0:
|
||||
if not file_list:
|
||||
# 没有成功的
|
||||
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
|
||||
def __get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: str = None) -> dict:
|
||||
|
@ -95,6 +95,13 @@ class _PluginBase(metaclass=ABCMeta):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_state(self) -> bool:
|
||||
"""
|
||||
获取插件运行状态
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop_service(self):
|
||||
"""
|
||||
|
@ -113,6 +113,9 @@ class AutoSignIn(_PluginBase):
|
||||
self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
|
@ -3,9 +3,11 @@ from pathlib import Path
|
||||
from typing import List, Tuple, Dict, Any
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.context import MediaInfo
|
||||
from app.core.event import eventmanager
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas import TransferInfo
|
||||
from app.schemas.types import EventType, MediaType
|
||||
from app.utils.http import RequestUtils
|
||||
|
||||
@ -14,7 +16,7 @@ class ChineseSubFinder(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "ChineseSubFinder"
|
||||
# 插件描述
|
||||
plugin_desc = "通知ChineseSubFinder下载字幕。"
|
||||
plugin_desc = "整理入库时通知ChineseSubFinder下载字幕。"
|
||||
# 插件图标
|
||||
plugin_icon = "chinesesubfinder.png"
|
||||
# 主题色
|
||||
@ -34,20 +36,16 @@ class ChineseSubFinder(_PluginBase):
|
||||
|
||||
# 私有属性
|
||||
_save_tmp_path = None
|
||||
_enable = False
|
||||
_enabled = False
|
||||
_host = None
|
||||
_api_key = None
|
||||
_remote_path = None
|
||||
_local_path = None
|
||||
_remote_path2 = None
|
||||
_local_path2 = None
|
||||
_remote_path3 = None
|
||||
_local_path3 = None
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
self._save_tmp_path = settings.TEMP_PATH
|
||||
if config:
|
||||
self._enable = config.get("enable")
|
||||
self._enabled = config.get("enabled")
|
||||
self._api_key = config.get("api_key")
|
||||
self._host = config.get('host')
|
||||
if self._host:
|
||||
@ -57,10 +55,6 @@ class ChineseSubFinder(_PluginBase):
|
||||
self._host = self._host + "/"
|
||||
self._local_path = config.get("local_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
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
@ -70,7 +64,117 @@ class ChineseSubFinder(_PluginBase):
|
||||
pass
|
||||
|
||||
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]:
|
||||
pass
|
||||
@ -88,38 +192,36 @@ class ChineseSubFinder(_PluginBase):
|
||||
item = event.event_data
|
||||
if not item:
|
||||
return
|
||||
# FIXME
|
||||
# 请求地址
|
||||
req_url = "%sapi/v1/add-job" % self._host
|
||||
|
||||
item_media = item.get("media_info")
|
||||
item_type = item_media.get("type")
|
||||
item_bluray = item.get("bluray")
|
||||
item_file = item.get("file")
|
||||
item_file_ext = item.get("file_ext")
|
||||
# 媒体信息
|
||||
item_media: MediaInfo = item.get("mediainfo")
|
||||
# 转移信息
|
||||
item_transfer: TransferInfo = item.get("transferinfo")
|
||||
# 类型
|
||||
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:
|
||||
file_path = "%s.mp4" % item_file
|
||||
else:
|
||||
if Path(item_file).suffix != item_file_ext:
|
||||
file_path = "%s%s" % (item_file, item_file_ext)
|
||||
else:
|
||||
file_path = item_file
|
||||
# 蓝光原盘虚拟个文件
|
||||
item_file_list = ["%s.mp4" % item_dest / item_dest.name]
|
||||
|
||||
# 路径替换
|
||||
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('\\', '/')
|
||||
for file_path in item_file_list:
|
||||
# 路径替换
|
||||
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('\\', '/')
|
||||
|
||||
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下载字幕
|
||||
self.__request_csf(req_url=req_url,
|
||||
file_path=file_path,
|
||||
item_type=0 if item_type == MediaType.MOVIE.value else 1,
|
||||
item_bluray=item_bluray)
|
||||
# 调用CSF下载字幕
|
||||
self.__request_csf(req_url=req_url,
|
||||
file_path=file_path,
|
||||
item_type=0 if item_type == MediaType.MOVIE.value else 1,
|
||||
item_bluray=item_bluray)
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def __request_csf(self, req_url, file_path, item_type, item_bluray):
|
||||
|
@ -1,12 +1,11 @@
|
||||
from typing import List, Tuple, Dict, Any
|
||||
|
||||
from python_hosts import Hosts, HostsEntry
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.utils.ip import IpUtils
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
from python_hosts import Hosts, HostsEntry
|
||||
|
||||
|
||||
class CustomHosts(_PluginBase):
|
||||
# 插件名称
|
||||
@ -60,6 +59,9 @@ class CustomHosts(_PluginBase):
|
||||
"enabled": self._enabled
|
||||
})
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
pass
|
||||
@ -68,7 +70,7 @@ class CustomHosts(_PluginBase):
|
||||
pass
|
||||
|
||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||
"""
|
||||
"""
|
||||
拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构
|
||||
"""
|
||||
return [
|
||||
@ -101,6 +103,9 @@ class CustomHosts(_PluginBase):
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextarea',
|
||||
@ -120,6 +125,9 @@ class CustomHosts(_PluginBase):
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextarea',
|
||||
@ -140,7 +148,7 @@ class CustomHosts(_PluginBase):
|
||||
], {
|
||||
"enabled": False,
|
||||
"hosts": "",
|
||||
"err_hosts": "",
|
||||
"err_hosts": ""
|
||||
}
|
||||
|
||||
def get_page(self) -> List[dict]:
|
||||
@ -189,6 +197,8 @@ class CustomHosts(_PluginBase):
|
||||
except Exception as err:
|
||||
err_hosts.append(host + "\n")
|
||||
logger.error(f"{host} 格式转换错误:{str(err)}")
|
||||
# 推送实时消息
|
||||
self.systemmessage.put(f"{host} 格式转换错误:{str(err)}")
|
||||
|
||||
# 写入系统hosts
|
||||
if new_entrys:
|
||||
@ -202,11 +212,10 @@ class CustomHosts(_PluginBase):
|
||||
except Exception as err:
|
||||
err_flag = True
|
||||
logger.error(f"更新系统hosts文件失败:{str(err) or '请检查权限'}")
|
||||
# 推送实时消息
|
||||
self.systemmessage.put(f"更新系统hosts文件失败:{str(err) or '请检查权限'}")
|
||||
return err_flag, err_hosts
|
||||
|
||||
def get_state(self):
|
||||
return self._enable and self._hosts and self._hosts[0]
|
||||
|
||||
def stop_service(self):
|
||||
"""
|
||||
退出插件
|
||||
|
@ -27,18 +27,21 @@ class DirMonitor(_PluginBase):
|
||||
|
||||
# 私有属性
|
||||
_monitor = None
|
||||
_enable = False
|
||||
_enabled = False
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
# 读取配置
|
||||
if config:
|
||||
self._enable = config.get("enable")
|
||||
self._enabled = config.get("enabled")
|
||||
|
||||
# 停止现有任务
|
||||
self.stop_service()
|
||||
|
||||
# TODO 启动任务
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
@ -1,12 +1,17 @@
|
||||
import datetime
|
||||
import re
|
||||
import xml.dom.minidom
|
||||
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.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.context import MediaInfo
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.utils.dom import DomUtils
|
||||
@ -14,7 +19,6 @@ from app.utils.http import RequestUtils
|
||||
|
||||
|
||||
class DoubanRank(_PluginBase):
|
||||
|
||||
# 插件名称
|
||||
plugin_name = "豆瓣榜单订阅"
|
||||
# 插件描述
|
||||
@ -39,10 +43,9 @@ class DoubanRank(_PluginBase):
|
||||
# 退出事件
|
||||
_event = Event()
|
||||
# 私有属性
|
||||
mediaserver = None
|
||||
subscribe = None
|
||||
rsshelper = None
|
||||
media = None
|
||||
downloadchain: DownloadChain = None
|
||||
subscribechain: SubscribeChain = None
|
||||
_scheduler = None
|
||||
_douban_address = {
|
||||
'movie-ustop': 'https://rsshub.app/douban/movie/ustop',
|
||||
'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',
|
||||
'movie-top250': 'https://rsshub.app/douban/movie/weekly/movie_top250',
|
||||
}
|
||||
_enable = False
|
||||
_enabled = False
|
||||
_cron = ""
|
||||
_rss_addrs = []
|
||||
_ranks = []
|
||||
_vote = 0
|
||||
_scheduler = None
|
||||
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
self.downloadchain = DownloadChain()
|
||||
self.subscribechain = SubscribeChain()
|
||||
|
||||
if config:
|
||||
self._enable = config.get("enable")
|
||||
self._enabled = config.get("enabled")
|
||||
self._cron = config.get("cron")
|
||||
self._vote = float(config.get("vote")) if config.get("vote") else 0
|
||||
rss_addrs = config.get("rss_addrs")
|
||||
@ -78,18 +83,28 @@ class DoubanRank(_PluginBase):
|
||||
self.stop_service()
|
||||
|
||||
# 启动服务
|
||||
if self._enable:
|
||||
if self._enabled:
|
||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||
if self._cron:
|
||||
logger.info(f"豆瓣榜单订阅服务启动,周期:{self._cron}")
|
||||
self._scheduler.add_job(self.__refresh_rss,
|
||||
CronTrigger.from_crontab(self._cron))
|
||||
try:
|
||||
self._scheduler.add_job(self.__refresh_rss,
|
||||
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():
|
||||
# 启动服务
|
||||
self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
pass
|
||||
@ -98,13 +113,227 @@ class DoubanRank(_PluginBase):
|
||||
pass
|
||||
|
||||
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]:
|
||||
"""
|
||||
拼装插件详情页面,需要返回页面配置,同时附带数据
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@ -125,13 +354,17 @@ class DoubanRank(_PluginBase):
|
||||
"""
|
||||
刷新RSS
|
||||
"""
|
||||
logger.info(f"开始刷新RSS ...")
|
||||
logger.info(f"开始刷新豆瓣榜单 ...")
|
||||
addr_list = self._rss_addrs + [self._douban_address.get(rank) for rank in self._ranks]
|
||||
if not addr_list:
|
||||
logger.info(f"未设置RSS地址")
|
||||
logger.info(f"未设置榜单RSS地址")
|
||||
return
|
||||
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:
|
||||
if not addr:
|
||||
continue
|
||||
@ -150,18 +383,64 @@ class DoubanRank(_PluginBase):
|
||||
|
||||
title = rss_info.get('title')
|
||||
douban_id = rss_info.get('doubanid')
|
||||
mtype = rss_info.get('type')
|
||||
unique_flag = f"doubanrank: {title} (DB:{douban_id})"
|
||||
# TODO 检查是否已处理过
|
||||
# TODO 识别媒体信息
|
||||
# TODO 检查媒体服务器是否存在
|
||||
# TODO 检查是否已订阅过
|
||||
# TODO 添加处理历史
|
||||
# TODO 添加订阅
|
||||
# TODO 发送通知
|
||||
# TODO 更新历史记录
|
||||
# 检查是否已处理过
|
||||
if unique_flag in [h.get("unique") for h in history]:
|
||||
continue
|
||||
# 识别媒体信息
|
||||
if douban_id:
|
||||
# 根据豆瓣ID获取豆瓣数据
|
||||
doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id)
|
||||
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:
|
||||
logger.error(str(e))
|
||||
|
||||
# 保存历史记录
|
||||
self.save_data('history', history)
|
||||
|
||||
logger.info(f"所有榜单RSS刷新完成")
|
||||
|
||||
@staticmethod
|
||||
|
@ -61,7 +61,6 @@ class DoubanSync(_PluginBase):
|
||||
_users: str = ""
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
self._cache_path = settings.TEMP_PATH / "__doubansync_cache__"
|
||||
self.rsshelper = RssHelper()
|
||||
self.downloadchain = DownloadChain()
|
||||
self.searchchain = SearchChain()
|
||||
@ -98,6 +97,9 @@ class DoubanSync(_PluginBase):
|
||||
self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@ -259,7 +261,6 @@ class DoubanSync(_PluginBase):
|
||||
poster = history.get("poster")
|
||||
mtype = history.get("type")
|
||||
time_str = history.get("time")
|
||||
overview = history.get("overview")
|
||||
doubanid = history.get("doubanid")
|
||||
contents.append(
|
||||
{
|
||||
@ -357,10 +358,8 @@ class DoubanSync(_PluginBase):
|
||||
"""
|
||||
if not self._users:
|
||||
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(","):
|
||||
# 同步每个用户的豆瓣数据
|
||||
if not user_id:
|
||||
@ -387,8 +386,8 @@ class DoubanSync(_PluginBase):
|
||||
logger.info(f'已超过同步天数,标题:{title},发布时间:{pubdate}')
|
||||
continue
|
||||
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
|
||||
# 根据豆瓣ID获取豆瓣数据
|
||||
doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id)
|
||||
@ -404,8 +403,6 @@ class DoubanSync(_PluginBase):
|
||||
if not mediainfo:
|
||||
logger.warn(f'未识别到媒体信息,标题:{title},豆瓣ID:{douban_id}')
|
||||
continue
|
||||
# 加入缓存
|
||||
caches.append(douban_id)
|
||||
# 查询缺失的媒体信息
|
||||
exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo)
|
||||
if exist_flag:
|
||||
@ -447,25 +444,22 @@ class DoubanSync(_PluginBase):
|
||||
username="豆瓣想看")
|
||||
action = "subscribe"
|
||||
# 存储历史记录
|
||||
if douban_id not in [h.get("doubanid") for h in history]:
|
||||
history.append({
|
||||
"action": action,
|
||||
"title": doubaninfo.get("title") or mediainfo.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")
|
||||
})
|
||||
history.append({
|
||||
"action": action,
|
||||
"title": doubaninfo.get("title") or mediainfo.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")
|
||||
})
|
||||
except Exception as err:
|
||||
logger.error(f'同步用户 {user_id} 豆瓣想看数据出错:{err}')
|
||||
logger.info(f"用户 {user_id} 豆瓣想看同步完成")
|
||||
# 保存历史记录
|
||||
self.save_data('history', history)
|
||||
# 保存缓存
|
||||
self._cache_path.write_text("\n".join(caches))
|
||||
|
||||
@eventmanager.register(EventType.DoubanSync)
|
||||
def remote_sync(self, event: Event):
|
||||
|
@ -36,7 +36,7 @@ class LibraryScraper(_PluginBase):
|
||||
_scheduler = None
|
||||
_scraper = None
|
||||
# 限速开关
|
||||
_enable = False
|
||||
_enabled = False
|
||||
_cron = None
|
||||
_mode = None
|
||||
_scraper_path = None
|
||||
@ -47,7 +47,7 @@ class LibraryScraper(_PluginBase):
|
||||
def init_plugin(self, config: dict = None):
|
||||
# 读取配置
|
||||
if config:
|
||||
self._enable = config.get("enable")
|
||||
self._enabled = config.get("enabled")
|
||||
self._cron = config.get("cron")
|
||||
self._mode = config.get("mode")
|
||||
self._scraper_path = config.get("scraper_path")
|
||||
@ -57,7 +57,7 @@ class LibraryScraper(_PluginBase):
|
||||
self.stop_service()
|
||||
|
||||
# 启动定时任务 & 立即运行一次
|
||||
if self._enable:
|
||||
if self._enabled:
|
||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||
if self._cron:
|
||||
logger.info(f"媒体库刮削服务启动,周期:{self._cron}")
|
||||
@ -68,6 +68,9 @@ class LibraryScraper(_PluginBase):
|
||||
self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
@ -114,6 +114,9 @@ class SiteStatistic(_PluginBase):
|
||||
self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
|
@ -31,7 +31,7 @@ class SpeedLimiter(_PluginBase):
|
||||
|
||||
# 私有属性
|
||||
_scheduler = None
|
||||
_enable: bool = False
|
||||
_enabled: bool = False
|
||||
_notify: bool = False
|
||||
_bandwidth: int = 0
|
||||
_interval: int = 60
|
||||
@ -39,7 +39,7 @@ class SpeedLimiter(_PluginBase):
|
||||
def init_plugin(self, config: dict = None):
|
||||
# 读取配置
|
||||
if config:
|
||||
self._enable = config.get("enable")
|
||||
self._enabled = config.get("enabled")
|
||||
self._notify = config.get("notify")
|
||||
try:
|
||||
# 总带宽
|
||||
@ -52,7 +52,7 @@ class SpeedLimiter(_PluginBase):
|
||||
self.stop_service()
|
||||
|
||||
# 启动限速任务
|
||||
if self._enable:
|
||||
if self._enabled:
|
||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||
self._scheduler.add_job(func=self.__check_playing_sessions,
|
||||
trigger='interval',
|
||||
@ -61,6 +61,9 @@ class SpeedLimiter(_PluginBase):
|
||||
self._scheduler.start()
|
||||
logger.info("播放限速服务启动")
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
@ -29,11 +29,14 @@ class TorrentRemover(_PluginBase):
|
||||
|
||||
# 私有属性
|
||||
downloader = None
|
||||
_enable = False
|
||||
_enabled = False
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
if config:
|
||||
self._enable = config.get("enable")
|
||||
self._enabled = config.get("enabled")
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
@ -56,7 +59,7 @@ class TorrentRemover(_PluginBase):
|
||||
"""
|
||||
联动删除下载器中的下载任务
|
||||
"""
|
||||
if not self._enable:
|
||||
if not self._enabled:
|
||||
return
|
||||
event_info = event.event_data
|
||||
if not event_info:
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@ -7,24 +9,26 @@ class Plugin(BaseModel):
|
||||
"""
|
||||
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前缀
|
||||
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
|
||||
|
@ -39,8 +39,12 @@ class TransferInfo(BaseModel):
|
||||
path: Optional[Path] = None
|
||||
# 转移后路径
|
||||
target_path: Optional[Path] = None
|
||||
# 是否蓝光原盘
|
||||
is_bluray: Optional[bool] = False
|
||||
# 处理文件数
|
||||
file_count: Optional[int] = 0
|
||||
# 处理文件清单
|
||||
file_list: Optional[list] = []
|
||||
# 总文件大小
|
||||
total_size: Optional[float] = 0
|
||||
# 失败清单
|
||||
|
Loading…
x
Reference in New Issue
Block a user