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})
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})

View File

@ -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:

View File

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

View File

@ -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]]:
"""

View File

@ -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):

View File

@ -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):
"""
退出插件

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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]]:
"""

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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
# 失败清单