fix 插件API安全控制

This commit is contained in:
jxxghp
2024-04-25 18:00:31 +08:00
parent 827007fe24
commit b367d1a4f6
25 changed files with 219 additions and 183 deletions

View File

@@ -121,6 +121,7 @@ class EventType(Enum):
"description": "刷新对应域名的站点数据", // API描述
}]
```
注意在插件中暴露API接口时注意安全控制推荐使用`settings.API_TOKEN`进行身份验证。
- 在对应的方法中实现API响应方法逻辑通过 `http://localhost:3001/docs` 查看API文档和调试

View File

@@ -2,11 +2,12 @@
"AutoSignIn": {
"name": "站点自动签到",
"description": "自动模拟登录、签到站点。",
"version": "2.0",
"version": "2.1",
"icon": "signin.png",
"author": "thsrite",
"level": 2,
"history": {
"v2.1": "增强API安全性",
"v2.0": "站点签到时更新站点使用统计信息需要主程序升级至v1.8.3+版本",
"v1.9": "支持馒头新架构自动签到"
}
@@ -22,11 +23,12 @@
"SiteStatistic": {
"name": "站点数据统计",
"description": "自动统计和展示站点数据。",
"version": "2.8",
"version": "2.9",
"icon": "statistic.png",
"author": "lightolly",
"level": 2,
"history": {
"v2.9": "增强API安全性",
"v2.8": "修复馒头未读消息统计",
"v2.7": "修复憨憨种子信息只统计第一页的问题,增加移除失效统计选项",
"v2.6": "支持馒头新架构数据统计"
@@ -43,11 +45,12 @@
"DoubanSync": {
"name": "豆瓣想看",
"description": "同步豆瓣想看数据,自动添加订阅。",
"version": "1.6",
"version": "1.7",
"icon": "douban.png",
"author": "jxxghp",
"level": 2,
"history": {
"v1.7": "增强API安全性",
"v1.6": "同步历史记录支持手动删除需要主程序升级至v1.8.4+版本",
"v1.5": "豆瓣信息识别后直接添加订阅,不进行搜索下载"
}
@@ -55,11 +58,12 @@
"DirMonitor": {
"name": "目录监控",
"description": "监控目录文件发生变化时实时整理到媒体库。",
"version": "1.9",
"version": "2.0",
"icon": "directory.png",
"author": "jxxghp",
"level": 1,
"history": {
"v2.0": "增强API安全性",
"v1.9": "修复目录监控不能正确获取下载历史记录进行识别的问题"
}
},
@@ -74,11 +78,12 @@
"DoubanRank": {
"name": "豆瓣榜单订阅",
"description": "监控豆瓣热门榜单,自动添加订阅。",
"version": "1.8",
"version": "1.9",
"icon": "movie.jpg",
"author": "jxxghp",
"level": 2,
"history": {
"v1.9": "增强API安全性",
"v1.8": "订阅历史记录支持手动删除需要主程序升级至v1.8.4+版本"
}
},
@@ -125,10 +130,13 @@
"CloudflareSpeedTest": {
"name": "Cloudflare IP优选",
"description": "🌩 测试 Cloudflare CDN 延迟和速度自动优选IP。",
"version": "1.1",
"version": "1.2",
"icon": "cloudflare.jpg",
"author": "thsrite",
"level": 1
"level": 1,
"history": {
"v1.2": "增强API安全性"
}
},
"BestFilmVersion": {
"name": "收藏洗版",
@@ -189,10 +197,13 @@
"AutoBackup": {
"name": "自动备份",
"description": "自动备份数据和配置文件。",
"version": "1.1",
"version": "1.2",
"icon": "Time_machine_B.png",
"author": "thsrite",
"level": 1
"level": 1,
"history": {
"v1.2": "增强API安全性"
}
},
"IYUUAutoSeed": {
"name": "IYUU自动辅种",
@@ -409,10 +420,13 @@
"LinkMonitor": {
"name": "实时硬链接",
"description": "监控目录文件变化,实时硬链接。",
"version": "1.5",
"version": "1.6",
"icon": "Linkace_C.png",
"author": "jxxghp",
"level": 1
"level": 1,
"history": {
"v1.6": "增强API安全性"
}
},
"CategoryEditor": {
"name": "二级分类策略",

View File

@@ -25,7 +25,7 @@ class AutoBackup(_PluginBase):
# 插件图标
plugin_icon = "Time_machine_B.png"
# 插件版本
plugin_version = "1.1"
plugin_version = "1.2"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -81,6 +81,14 @@ class AutoBackup(_PluginBase):
self._scheduler.print_jobs()
self._scheduler.start()
def api_backup(self, apikey: str):
"""
API调用备份
"""
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
return self.__backup()
def __backup(self):
"""
自动备份、删除备份
@@ -169,7 +177,7 @@ class AutoBackup(_PluginBase):
def get_api(self) -> List[Dict[str, Any]]:
return [{
"path": "/backup",
"endpoint": self.__backup,
"endpoint": self.api_backup,
"methods": ["GET"],
"summary": "MoviePilot备份",
"description": "MoviePilot备份",

View File

@@ -38,7 +38,7 @@ class AutoSignIn(_PluginBase):
# 插件图标
plugin_icon = "signin.png"
# 插件版本
plugin_version = "2.0"
plugin_version = "2.1"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -852,10 +852,13 @@ class AutoSignIn(_PluginBase):
logger.error("站点模块加载失败:%s" % str(e))
return None
def signin_by_domain(self, url: str) -> schemas.Response:
def signin_by_domain(self, url: str, apikey: str) -> schemas.Response:
"""
签到一个站点可由API调用
"""
# 校验
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
domain = StringUtils.get_url_domain(url)
site_info = self.sites.get_indexer(domain)
if not site_info:

View File

@@ -32,7 +32,7 @@ class CloudflareSpeedTest(_PluginBase):
# 插件图标
plugin_icon = "cloudflare.jpg"
# 插件版本
plugin_version = "1.1"
plugin_version = "1.2"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -750,10 +750,12 @@ class CloudflareSpeedTest(_PluginBase):
def get_page(self) -> List[dict]:
pass
def cloudflare_speedtest(self) -> schemas.Response:
def cloudflare_speedtest(self, apikey: str) -> schemas.Response:
"""
API调用CloudflareSpeedTest IP优选
"""
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
self.__cloudflareSpeedTest()
return schemas.Response(success=True)

View File

@@ -134,19 +134,19 @@ class ContractCheck(_PluginBase):
self._scheduler.start()
def parse_contract_infos(self, infos):
if infos == None:
if infos is None:
return
info_list = infos.split("\n")
for info in info_list:
_site_name, _official, _size, _num, _duration, date = info.split("|")
site_id = self._get_site_id(_site_name)
if site_id == None:
if site_id is None:
logger.error(f"站点{_site_name}不在数据库中,请检查配置!")
continue
date_format = "%Y/%m/%d"
date = datetime.strptime(date, date_format).date()
date = datetime.strptime(date, date_format)
_official = True if _official == "" else False
c_info: self.ContractInfo = self.ContractInfo(
c_info = self.ContractInfo(
_site_name,
_official,
int(_size) * 1024 * 1024 * 1024,
@@ -622,7 +622,7 @@ class ContractCheck(_PluginBase):
i = html_text.find("window.location")
if i == -1:
return None
tmp_url = url + html_text[i : html_text.find(";")].replace(
tmp_url = url + html_text[i: html_text.find(";")].replace(
'"', ""
).replace("+", "").replace(" ", "").replace(
"window.location=", ""
@@ -689,8 +689,8 @@ class ContractCheck(_PluginBase):
return None
# 检查契约达成情况,返回是否达成、差多少体积、差多少数量、还剩多少时间
def _check_seed_states(self, contract_info, site_user_info):
is_satisfied = False
@staticmethod
def _check_seed_states(contract_info, site_user_info):
is_size_satisfied = False
is_num_satisfied = False
size_gap = 0
@@ -883,4 +883,4 @@ class ContractCheck(_PluginBase):
"queue_cnt": self._queue_cnt,
"contract_infos": self._contract_infos,
}
)
)

View File

@@ -15,7 +15,6 @@ from bencode import bdecode, bencode
from app.core.config import settings
from app.core.event import eventmanager
from app.db.models import Site
from app.db.site_oper import SiteOper
from app.helper.sites import SitesHelper
from app.helper.torrent import TorrentHelper
@@ -35,15 +34,15 @@ class CSSiteConfig(object):
"""
def __init__(
self,
name: str = None,
url: str = None,
passkey: str = None,
id: int = None,
cookie: str = None,
ua: str = None,
proxy: bool = None,
query_gap: int = 1,
self,
name: str = None,
url: str = None,
passkey: str = None,
id: int = None,
cookie: str = None,
ua: str = None,
proxy: bool = None,
query_gap: int = 1,
) -> None:
self.name = name
self.url = url
@@ -264,11 +263,9 @@ class CrossSeed(_PluginBase):
(site.get("id"), site.get("name")) for site in self.__custom_sites()
]
self._sites = [site_id for site_id, site_name in all_sites if site_id in self._sites]
# 拆分出选中的站点
site_names = [site_name for site_id, site_name in all_sites if site_id in self._sites]
# 整理所有可用内部站点信息
all_site_cs_info_map : dict[str, CSSiteConfig] = dict()
all_site_cs_info_map: dict[str, CSSiteConfig] = dict()
for site in inner_site_list:
if site.is_active:
all_site_cs_info_map[site.name] = CSSiteConfig(
@@ -277,7 +274,7 @@ class CrossSeed(_PluginBase):
id=site.id,
cookie=site.cookie,
ua=site.ua,
proxy=site.proxy,
proxy=True if site.proxy else False,
)
for site in self.__custom_sites():
all_site_cs_info_map[site.get("name")] = CSSiteConfig(
@@ -295,7 +292,7 @@ class CrossSeed(_PluginBase):
site_name_key_map = dict()
site_name_gap_map = dict()
for site_key in self._token.strip().split("\n"):
site_key_arr = re.split("[\s:]+",site_key.strip())
site_key_arr = re.split(r"[\s:]+", site_key.strip())
site_name = site_key_arr[0]
site_name_key_map[site_name] = site_key_arr[1]
if len(site_key_arr) > 2:
@@ -312,7 +309,8 @@ class CrossSeed(_PluginBase):
for site_name in site_names:
site_key = site_name_key_map.get(site_name)
if not site_key:
logger.warning(f"未找到站点{site_name}的passkey, 请检查passkey配置是否有误站点{site_name}将跳过辅种")
logger.warning(
f"未找到站点{site_name}的passkey, 请检查passkey配置是否有误站点{site_name}将跳过辅种")
continue
site_cs_info = all_site_cs_info_map.get(site_name)
site_cs_info.passkey = site_key
@@ -381,7 +379,7 @@ class CrossSeed(_PluginBase):
"""
if self.get_state():
# 如果开启了定时任务,并且参数齐全
if self._cron :
if self._cron:
return [{
"id": "CrossSeed",
"name": "青蛙辅种助手",
@@ -392,10 +390,10 @@ class CrossSeed(_PluginBase):
else:
# 随机时间
triggers = TimerUtils.random_scheduler(num_executions=1,
begin_hour=2,
end_hour=7,
max_interval=290,
min_interval=0)
begin_hour=2,
end_hour=7,
max_interval=290,
min_interval=0)
ret_jobs = []
for trigger in triggers:
ret_jobs.append({

View File

@@ -1,4 +1,5 @@
import json,re
import json
import re
from datetime import datetime, timedelta
from app.modules.emby import Emby
@@ -347,10 +348,10 @@ class DiagParamAdjust(_PluginBase):
return False
# 符合所有情况
if (('repeatlast' in replaceText \
and 'x=(W-w)/2:y=(H-h):repeatlast=0' in searchText \
and result['Object']['TranscodingOptions']['DisableHardwareSubtitleOverlay'] is True) \
or (searchText == "" and replaceText == "")) \
if (('repeatlast' in replaceText
and 'x=(W-w)/2:y=(H-h):repeatlast=0' in searchText
and result['Object']['TranscodingOptions']['DisableHardwareSubtitleOverlay'] is True)
or (searchText == "" and replaceText == "")) \
and self._resolution == self._last_resolution:
# (A or B) and C
return True

View File

@@ -24,7 +24,7 @@ from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.transferhistory_oper import TransferHistoryOper
from app.log import logger
from app.plugins import _PluginBase
from app.schemas import Notification, NotificationType, TransferInfo
from app.schemas import NotificationType, TransferInfo
from app.schemas.types import EventType, MediaType, SystemConfigKey
from app.utils.string import StringUtils
from app.utils.system import SystemUtils
@@ -59,7 +59,7 @@ class DirMonitor(_PluginBase):
# 插件图标
plugin_icon = "directory.png"
# 插件版本
plugin_version = "1.9"
plugin_version = "2.0"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -655,10 +655,12 @@ class DirMonitor(_PluginBase):
}]
return []
def sync(self) -> schemas.Response:
def sync(self, apikey: str) -> schemas.Response:
"""
API调用目录同步
"""
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
self.sync_all()
return schemas.Response(success=True)

View File

@@ -29,7 +29,7 @@ class DoubanRank(_PluginBase):
# 插件图标
plugin_icon = "movie.jpg"
# 插件版本
plugin_version = "1.8"
plugin_version = "1.9"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -395,7 +395,8 @@ class DoubanRank(_PluginBase):
'api': 'plugin/DoubanRank/delete_history',
'method': 'get',
'params': {
'key': f"doubanrank: {title} (DB:{doubanid})"
'key': f"doubanrank: {title} (DB:{doubanid})",
'apikey': settings.API_TOKEN
}
}
},
@@ -488,10 +489,12 @@ class DoubanRank(_PluginBase):
except Exception as e:
print(str(e))
def delete_history(self, key: str):
def delete_history(self, key: str, apikey: str):
"""
删除同步历史记录
"""
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
# 历史记录
historys = self.get_data('history')
if not historys:

View File

@@ -34,7 +34,7 @@ class DoubanSync(_PluginBase):
# 插件图标
plugin_icon = "douban.png"
# 插件版本
plugin_version = "1.6"
plugin_version = "1.7"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -371,7 +371,8 @@ class DoubanSync(_PluginBase):
'api': 'plugin/DoubanSync/delete_history',
'method': 'get',
'params': {
'doubanid': doubanid
'doubanid': doubanid,
'apikey': settings.API_TOKEN
}
}
},
@@ -463,10 +464,12 @@ class DoubanSync(_PluginBase):
"clear": self._clear
})
def delete_history(self, doubanid: str):
def delete_history(self, doubanid: str, apikey: str):
"""
删除同步历史记录
"""
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
# 历史记录
historys = self.get_data('history')
if not historys:

View File

@@ -171,7 +171,7 @@ class DownloaderHelper(_PluginBase):
return [{
'component': 'VForm',
'content': [{ # 业务无关总控
'content': [{ # 业务无关总控
'component': 'VRow',
'content': [{
'component': 'VCol',
@@ -216,7 +216,7 @@ class DownloaderHelper(_PluginBase):
}
}]
}]
}, { # 业务相关总控
}, { # 业务相关总控
'component': 'VRow',
'content': [{
'component': 'VCol',

View File

@@ -192,8 +192,6 @@ class FeiShuMsg(_PluginBase):
title = msg_body.get("title")
# 文本
text = msg_body.get("text")
# 图像
image = msg_body.get("image")
if not title and not text:
logger.warn("标题和内容不能同时为空")

View File

@@ -1,17 +1,19 @@
import socket
from typing import List, Tuple, Dict, Any
import re
import socket
from datetime import datetime, timedelta
from typing import List, Tuple, Dict, Any
import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from dotenv import set_key
from app.core.config import settings
from app.core.module import ModuleManager
from app.log import logger
from app.plugins import _PluginBase
from app.schemas import Notification, NotificationType
from app.core.config import settings
from dotenv import set_key
from app.core.module import ModuleManager
from app.scheduler import Scheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime, timedelta
import pytz
from app.schemas import NotificationType
class IpDetect(_PluginBase):
@@ -218,7 +220,8 @@ class IpDetect(_PluginBase):
set_key(settings.CONFIG_PATH / "app.env", k, v)
logger.info(f"重新设置服务地址{k}成功!")
def get_value(self, key):
@staticmethod
def get_value(key):
if key == "QB_HOST":
return settings.QB_HOST
elif key == "TR_HOST":
@@ -238,7 +241,8 @@ class IpDetect(_PluginBase):
else:
return None
def parse_ip(self, ip):
@staticmethod
def parse_ip(ip):
ip_pattern = r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
match = re.search(ip_pattern, ip)
if match:
@@ -268,7 +272,8 @@ class IpDetect(_PluginBase):
}
]
def get_local_ip(self):
@staticmethod
def get_local_ip():
try:
# 创建一个 UDP 套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

View File

@@ -17,7 +17,7 @@ from app.core.config import settings
from app.core.event import eventmanager, Event
from app.log import logger
from app.plugins import _PluginBase
from app.schemas import Notification, NotificationType
from app.schemas import NotificationType
from app.schemas.types import EventType
from app.utils.system import SystemUtils
@@ -51,7 +51,7 @@ class LinkMonitor(_PluginBase):
# 插件图标
plugin_icon = "Linkace_C.png"
# 插件版本
plugin_version = "1.5"
plugin_version = "1.6"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -393,10 +393,12 @@ class LinkMonitor(_PluginBase):
"kwargs": {}
}]
def sync(self) -> schemas.Response:
def sync(self, apikey: str) -> schemas.Response:
"""
API调用目录同步
"""
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
self.sync_all()
return schemas.Response(success=True)

View File

@@ -4,10 +4,9 @@ from threading import Lock
from typing import Optional, Any, List, Dict, Tuple
import pytz
import requests
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
import requests
from app.schemas.types import MediaType
from app.chain.download import DownloadChain
from app.chain.search import SearchChain
@@ -19,7 +18,7 @@ from app.core.metainfo import MetaInfo
from app.log import logger
from app.plugins import _PluginBase
from app.schemas.types import EventType
from app.utils.http import RequestUtils
from app.schemas.types import MediaType
lock = Lock()
@@ -308,7 +307,7 @@ class NeoDBSync(_PluginBase):
}
]
},
{
{
'component': 'VRow',
'content': [
{
@@ -445,7 +444,6 @@ class NeoDBSync(_PluginBase):
}
]
def __update_config(self):
"""
更新配置
@@ -494,7 +492,6 @@ class NeoDBSync(_PluginBase):
username = self.__get_username(token)
# 同步每个 NeoDB 用户的数据
logger.info(f"开始同步 NeoDB 用户 {username} 的想看数据 ...")
results = []
try:
movie_response = requests.get(self._movie_url, headers=headers)
movie_response.raise_for_status()
@@ -545,7 +542,6 @@ class NeoDBSync(_PluginBase):
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:
# 添加订阅
logger.info(f'{mediainfo.title_year} 媒体库中不存在或不完整,添加订阅 ...')
@@ -594,12 +590,13 @@ class NeoDBSync(_PluginBase):
return False
return True
def __get_username(self, token: str):
@staticmethod
def __get_username(token: str):
"""
获取 NeoDB 用户名
"""
try:
user_info = requests.get(f"https://neodb.social/api/me", headers= {"Authorization": f"Bearer {token}"})
user_info = requests.get(f"https://neodb.social/api/me", headers={"Authorization": f"Bearer {token}"})
user_info.raise_for_status()
try:
username = user_info.json().get("username")

View File

@@ -67,7 +67,7 @@ class PluginAutoUpgrade(_PluginBase):
self.stop_service()
# 如果需要立即运行一次
if self.__get_config_item(config_key='run_once'):
if (self.__start_scheduler()):
if self.__start_scheduler():
self.__scheduler.add_job(func=self.__try_run,
trigger='date',
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3),
@@ -81,9 +81,8 @@ class PluginAutoUpgrade(_PluginBase):
"""
获取插件状态
"""
state = True if self.__get_config_item(config_key='enable') \
and self.__get_config_item(config_key='cron') \
else False
state = True if self.__get_config_item(config_key='enable') and self.__get_config_item(config_key='cron') \
else False
return state
@staticmethod
@@ -133,7 +132,7 @@ class PluginAutoUpgrade(_PluginBase):
installed_online_plugin_options = self.__get_installed_online_plugin_options()
form = [{
'component': 'VForm',
'content': [{ # 业务无关总控
'content': [{ # 业务无关总控
'component': 'VRow',
'content': [{
'component': 'VCol',
@@ -386,11 +385,12 @@ class PluginAutoUpgrade(_PluginBase):
获取所有已安装的本地插件信息
"""
local_plugins = cls.__get_local_plugins()
installed_local_plugins = [local_plugin for local_plugin in local_plugins if local_plugin and local_plugin.installed]
installed_local_plugins = [local_plugin for local_plugin in local_plugins if
local_plugin and local_plugin.installed]
return installed_local_plugins
@classmethod
def __get_installed_local_plugin(cls, plugin_id: str) -> List[schemas.Plugin]:
def __get_installed_local_plugin(cls, plugin_id: str) -> Optional[schemas.Plugin]:
"""
获取指定的已安装的本地插件信息
"""
@@ -417,11 +417,12 @@ class PluginAutoUpgrade(_PluginBase):
获取所有已安装的在线插件
"""
online_plugins = cls.__get_online_plugins()
installed_online_plugins = [online_plugin for online_plugin in online_plugins if online_plugin and online_plugin.installed]
installed_online_plugins = [online_plugin for online_plugin in online_plugins if
online_plugin and online_plugin.installed]
return installed_online_plugins
@classmethod
def __get_installed_online_plugin_options(cls) -> Dict[str, Any]:
def __get_installed_online_plugin_options(cls) -> list:
"""
获取所有已安装的在线插件的选项数据
"""
@@ -437,14 +438,15 @@ class PluginAutoUpgrade(_PluginBase):
return installed_online_plugin_options
@classmethod
def __get_has_update_online_plugins(cls) -> List[schemas.Plugin]:
def __get_has_update_online_plugins(cls) -> Optional[List[schemas.Plugin]]:
"""
获取所有可升级的在线插件
"""
installed_online_plugins = cls.__get_installed_online_plugins()
if not installed_online_plugins:
return None
has_update_online_plugins = [installed_online_plugin for installed_online_plugin in installed_online_plugins if installed_online_plugin and installed_online_plugin.has_update]
has_update_online_plugins = [installed_online_plugin for installed_online_plugin in installed_online_plugins if
installed_online_plugin and installed_online_plugin.has_update]
return has_update_online_plugins
def __start_scheduler(self, timezone=None) -> bool:
@@ -500,7 +502,7 @@ class PluginAutoUpgrade(_PluginBase):
return False
@staticmethod
def __install_plugin(plugin_id: str, repo_url: str = "", force: bool = False) -> Tuple[bool, str]:
def __install_plugin(plugin_id: str, repo_url: str = "", force: bool = False) -> Tuple[bool, Optional[str]]:
"""
安装插件参考app.api.endpoints.plugin.install
:param plugin_id: 插件ID
@@ -560,17 +562,20 @@ class PluginAutoUpgrade(_PluginBase):
# 发送通知
self.__send_notify(results=upgrade_results)
def __upgrade_single(self, online_plugin: schemas.Plugin) -> Dict[str, Any]:
def __upgrade_single(self, online_plugin: schemas.Plugin) -> Optional[Dict[str, Any]]:
"""
单个升级
"""
if not online_plugin or not online_plugin.has_update or not online_plugin.id or not online_plugin.repo_url or not self.__check_allow_upgrade(plugin_id=online_plugin.id):
if not online_plugin or not online_plugin.has_update or not online_plugin.id or not online_plugin.repo_url or not self.__check_allow_upgrade(
plugin_id=online_plugin.id):
return None
installed_local_plugin = self.__get_installed_local_plugin(plugin_id=online_plugin.id)
if not installed_local_plugin:
return None
success, message = self.__install_plugin(plugin_id=online_plugin.id, repo_url=online_plugin.repo_url, force=True)
logger.info(f"插件升级结果: plugin_name = {online_plugin.plugin_name}, plugin_version = v{installed_local_plugin.plugin_version} -> v{online_plugin.plugin_version}, success = {success}, message = {message}")
success, message = self.__install_plugin(plugin_id=online_plugin.id, repo_url=online_plugin.repo_url,
force=True)
logger.info(
f"插件升级结果: plugin_name = {online_plugin.plugin_name}, plugin_version = v{installed_local_plugin.plugin_version} -> v{online_plugin.plugin_version}, success = {success}, message = {message}")
return {
'success': success,
'message': message,
@@ -578,7 +583,7 @@ class PluginAutoUpgrade(_PluginBase):
'plugin_name': online_plugin.plugin_name,
'new_plugin_version': online_plugin.plugin_version,
'old_plugin_version': installed_local_plugin.plugin_version,
'datetime_str': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'datetime_str': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'upgrade_info': self.__extract_upgrade_history(online_plugin)
}
@@ -627,7 +632,7 @@ class PluginAutoUpgrade(_PluginBase):
self.save_data(self.__data_key_upgrade_records, upgrade_records)
@staticmethod
def __convert_upgrade_record_to_page_data(upgrade_record: Dict[str, Any]) -> Dict[str, Any]:
def __convert_upgrade_record_to_page_data(upgrade_record: Dict[str, Any]) -> Optional[Dict[str, Any]]:
if not upgrade_record:
return None
info = "成功" if upgrade_record.get("success") else upgrade_record.get("message")
@@ -643,13 +648,14 @@ class PluginAutoUpgrade(_PluginBase):
return []
# 只展示最近10条
upgrade_records = upgrade_records[-10:]
page_data = [self.__convert_upgrade_record_to_page_data(upgrade_record) for upgrade_record in upgrade_records if upgrade_record]
page_data = [self.__convert_upgrade_record_to_page_data(upgrade_record) for upgrade_record in upgrade_records if
upgrade_record]
# 按时间倒序
page_data = sorted(page_data, key=lambda item: item.get("datetime_str"), reverse=True)
return page_data
@staticmethod
def __extract_upgrade_history(plugin: schemas.Plugin, version: str = None) -> str:
def __extract_upgrade_history(plugin: schemas.Plugin, version: str = None) -> Optional[str]:
"""
提取指定版本的升级历史信息
"""

View File

@@ -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 PushPlusMsg(_PluginBase):
@@ -179,7 +178,7 @@ class PushPlusMsg(_PluginBase):
"title": title,
"content": text,
"template": "txt",
"channel":"wechat"
"channel": "wechat"
}
res = RequestUtils(content_type="application/json").post_res(sc_url, json=event_info)
if res and res.status_code == 200:

View File

@@ -262,7 +262,8 @@ class QbCommand(_PluginBase):
return []
return all_torrents
def get_torrents_status(self, torrents):
@staticmethod
def get_torrents_status(torrents):
downloading_torrents = []
uploading_torrents = []
paused_torrents = []
@@ -372,7 +373,7 @@ class QbCommand(_PluginBase):
f"暂停操作中请稍等...\n",
)
if len(to_be_paused) > 0:
if self._qb.stop_torrents(ids=(to_be_paused)):
if self._qb.stop_torrents(ids=to_be_paused):
logger.info(f"暂停了{len(to_be_paused)}个种子")
else:
logger.error(f"暂停种子失败")
@@ -625,7 +626,6 @@ class QbCommand(_PluginBase):
if flag:
logger.info(f"设置QB限速成功")
if self._notify:
text = "QB设置限速成功\n"
if upload_limit == 0:
text = f"上传无限速"
else:
@@ -639,7 +639,7 @@ class QbCommand(_PluginBase):
title=f"【QB远程操作】",
text=text,
)
elif flag == False:
elif not flag:
logger.error(f"QB设置限速失败")
if self._notify:
self.post_message(

View File

@@ -10,7 +10,7 @@ from watchdog.observers import Observer
from app.log import logger
from app.plugins import _PluginBase
from app.schemas import Notification, NotificationType
from app.schemas import NotificationType
state_lock = threading.Lock()
@@ -375,7 +375,8 @@ class RemoveLink(_PluginBase):
return True
return False
def scrape_files_left(self, path):
@staticmethod
def scrape_files_left(path):
"""
检查path目录是否只包含刮削文件
"""
@@ -415,6 +416,7 @@ class RemoveLink(_PluginBase):
logger.info(f'删除刮削文件:{file}')
# 清理空目录
self.delete_empty_folders(path)
def delete_empty_folders(self, path):
"""
从指定路径开始,逐级向上层目录检测并删除空目录,直到遇到非空目录或到达指定监控目录为止
@@ -485,7 +487,7 @@ class RemoveLink(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【清理硬链接】",
text=f"监控到删除源文件:[{file_path}]\n"
f"同步删除硬链接文件:[{path}]",
f"同步删除硬链接文件:[{path}]",
)
except Exception as e:
logger.error(

View File

@@ -43,7 +43,7 @@ class SiteStatistic(_PluginBase):
# 插件图标
plugin_icon = "statistic.png"
# 插件版本
plugin_version = "2.8"
plugin_version = "2.9"
# 插件作者
plugin_author = "lightolly"
# 作者主页
@@ -1127,10 +1127,12 @@ class SiteStatistic(_PluginBase):
return site_schema(site_name, url, site_cookie, html_text, session=session, ua=ua, proxy=proxy)
return None
def refresh_by_domain(self, domain: str) -> schemas.Response:
def refresh_by_domain(self, domain: str, apikey: str) -> schemas.Response:
"""
刷新一个站点数据可由API调用
"""
if apikey != settings.API_TOKEN:
return schemas.Response(success=False, message="API密钥错误")
site_info = self.sites.get_indexer(domain)
if site_info:
site_data = self.__refresh_site_data(site_info)
@@ -1309,18 +1311,18 @@ class SiteStatistic(_PluginBase):
incUploads += upload
incDownloads += download
messages[upload + (rand / 1000)] = (
f"{site}{updated_date}\n"
+ f"上传量:{StringUtils.str_filesize(upload)}\n"
+ f"下载量:{StringUtils.str_filesize(download)}\n"
+ "————————————"
f"{site}{updated_date}\n"
+ f"上传量:{StringUtils.str_filesize(upload)}\n"
+ f"下载量:{StringUtils.str_filesize(download)}\n"
+ "————————————"
)
if incDownloads or incUploads:
sorted_messages = [messages[key] for key in sorted(messages.keys(), reverse=True)]
sorted_messages.insert(0, f"【汇总】\n"
f"总上传:{StringUtils.str_filesize(incUploads)}\n"
f"总下载:{StringUtils.str_filesize(incDownloads)}\n"
f"————————————")
f"总上传:{StringUtils.str_filesize(incUploads)}\n"
f"总下载:{StringUtils.str_filesize(incDownloads)}\n"
f"————————————")
self.post_message(mtype=NotificationType.SiteMessage,
title="站点数据统计", text="\n".join(sorted_messages))

View File

@@ -99,7 +99,6 @@ class TrackerEditor(_PluginBase):
for torrent in torrent_list:
new_tracker_list = []
for tracker in torrent.tracker_list:
new_url = None
if self._target_domain in tracker:
new_url = tracker.replace(self._target_domain, self._replace_domain)
new_tracker_list.append(new_url)
@@ -114,7 +113,7 @@ class TrackerEditor(_PluginBase):
__tracker_list = new_tracker_list
if torrent_update_cnt > 0:
update_result = self._downloader.update_tracker(hash_string=torrent.hashString, tracker_list=__tracker_list)
if update_result == False:
if not update_result:
logger.error(f"执行tracker修改出错中止本次执行")
torrent_update_cnt = 0
break

View File

@@ -200,7 +200,8 @@ class TrCommand(_PluginBase):
return []
return all_torrents
def get_torrents_status(self, torrents):
@staticmethod
def get_torrents_status(torrents):
downloading_torrents = []
uploading_torrents = []
paused_torrents = []
@@ -265,15 +266,15 @@ class TrCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【TR暂停任务启动】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"暂停操作中请稍等...\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"暂停操作中请稍等...\n",
)
if len(to_be_paused) > 0:
if self._tr.stop_torrents(ids=(to_be_paused)):
if self._tr.stop_torrents(ids=to_be_paused):
logger.info(f"暂停了{len(to_be_paused)}个种子")
else:
logger.error(f"暂停种子失败")
@@ -305,11 +306,11 @@ class TrCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【TR暂停任务完成】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
)
@eventmanager.register(EventType.PluginAction)
@@ -345,12 +346,12 @@ class TrCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【TR开始任务启动】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"开始操作中请稍等...\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n"
f"开始操作中请稍等...\n",
)
if not self._tr.start_torrents(ids=hash_paused):
logger.error(f"开始种子失败")
@@ -382,11 +383,11 @@ class TrCommand(_PluginBase):
mtype=NotificationType.SiteMessage,
title=f"【TR开始任务完成】",
text=f"种子总数: {len(all_torrents)} \n"
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
f"做种数量: {len(hash_uploading)}\n"
f"下载数量: {len(hash_downloading)}\n"
f"检查数量: {len(hash_checking)}\n"
f"暂停数量: {len(hash_paused)}\n"
f"错误数量: {len(hash_error)}\n",
)
@eventmanager.register(EventType.PluginAction)
@@ -410,7 +411,6 @@ class TrCommand(_PluginBase):
return
self.set_limit(self._upload_limit, self._download_limit)
def set_both_limit(self, upload_limit, download_limit):
if not self._enable_upload_limit or not self._enable_upload_limit:
return True
@@ -461,7 +461,6 @@ class TrCommand(_PluginBase):
download_limit=int(download_limit), upload_limit=int(upload_limit_current_val)
)
def set_limit(self, upload_limit, download_limit):
# 限速,满足以下三种情况设置限速
# 1. 插件启用 && download_limit启用
@@ -481,7 +480,6 @@ class TrCommand(_PluginBase):
if flag:
logger.info(f"设置TR限速成功")
if self._notify:
text = "TR设置限速成功\n"
if upload_limit == 0:
text = f"上传无限速"
else:
@@ -495,7 +493,7 @@ class TrCommand(_PluginBase):
title=f"【TR远程操作】",
text=text,
)
elif flag == False:
elif not flag:
logger.error(f"TR设置限速失败")
if self._notify:
self.post_message(

View File

@@ -5,8 +5,8 @@ import threading
import time
import traceback
from pathlib import Path
from time import sleep
from typing import List, Tuple, Dict, Any, Optional
import pytz
import qbittorrentapi
from apscheduler.schedulers.background import BackgroundScheduler
@@ -14,13 +14,12 @@ from apscheduler.triggers.cron import CronTrigger
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from watchdog.observers.polling import PollingObserver
from app import schemas
from app.chain.tmdb import TmdbChain
from app.chain.transfer import TransferChain
from app.core.config import settings
from app.core.context import MediaInfo
from app.core.event import eventmanager, Event
from app.core.metainfo import MetaInfoPath
from app.db.downloadhistory_oper import DownloadHistoryOper
from app.db.transferhistory_oper import TransferHistoryOper
from app.log import logger
@@ -212,7 +211,6 @@ class VCBAnimeMonitor(_PluginBase):
else:
self._dirconf[mon_path] = None
# 转移方式
self._transferconf[mon_path] = _transfer_type
@@ -254,7 +252,6 @@ class VCBAnimeMonitor(_PluginBase):
logger.error(f"{mon_path} 启动目录监控失败:{err_msg}")
self.systemmessage.put(f"{mon_path} 启动目录监控失败:{err_msg}")
# 运行一次定时服务
if self._onlyonce:
logger.info("目录监控服务启动,立即运行一次")
@@ -298,7 +295,6 @@ class VCBAnimeMonitor(_PluginBase):
def __get_data(self, key: str):
return self.get_data(key)
def sync_all(self):
"""
立即运行一次,全量同步目录中所有文件
@@ -396,7 +392,7 @@ class VCBAnimeMonitor(_PluginBase):
if remeta.is_special and self._switch_ova:
logger.info(f"{file_path.name} 为OVA资源,开始处理")
if self.get_data(key=f"OVA_{file_meta.title}") is not None:
ova_history_ep = int(self.get_data(key=f"OVA_{file_meta.title}"))+1
ova_history_ep = int(self.get_data(key=f"OVA_{file_meta.title}")) + 1
file_meta.begin_episode = ova_history_ep
self.save_data(key=f"OVA_{file_meta.title}", value=ova_history_ep)
else:
@@ -598,7 +594,6 @@ class VCBAnimeMonitor(_PluginBase):
def torrent_event(self, event, mon_path: str, text: str):
"""
处理种子文件
:param mon_path: 种子目录
"""
evc_path = Path(event.src_path)
if not event.is_directory and (evc_path.suffix == ".torrent" or str(evc_path).split('.')[1] == "torrent"):
@@ -634,7 +629,6 @@ class VCBAnimeMonitor(_PluginBase):
except qbittorrentapi.exceptions.APIError as e:
logger.error(f"VCB辅助整理模块转移qb文件移动失败{e}")
def send_msg(self):
"""
定时检查是否有媒体处理完,发送统一消息

View File

@@ -2,8 +2,9 @@ import concurrent
import re
from pathlib import Path
from typing import List
import threading
import roman
from app.chain.media import MediaChain
from app.chain.tmdb import TmdbChain
from app.core.metainfo import MetaInfoPath
@@ -11,7 +12,7 @@ from app.log import logger
from app.schemas import MediaType
class ReMeta():
class ReMeta:
# 解析之后的标题:
title: str = None
# 识别出来的集数
@@ -40,7 +41,7 @@ class ReMeta():
]
_ova_patterns = [re.compile(r"\[.*?(OVA|OAD).*?]"),
re.compile(r"\[\d+\.5]"),
re.compile(r"\[00\]")]
re.compile(r"\[00]")]
final_season_patterns = [re.compile('final season', re.IGNORECASE),
re.compile('The Final', re.IGNORECASE),
@@ -49,7 +50,7 @@ class ReMeta():
# 自定义添加的季度正则表达式
_custom_season_patterns = []
def __init__(self, ova_switch: bool = False,high_performance: bool = False):
def __init__(self, ova_switch: bool = False, high_performance: bool = False):
self.ova_switch = ova_switch
self.high_performance = high_performance
@@ -62,7 +63,7 @@ class ReMeta():
if meta.title.count("[") != 4 and meta.title.count("]") != 4:
# 可能是电影,电影只有三组[],因此去除所有[]后只剩下电影名
logger.warn("不符合VCB-Studio的剧集命名规范跳过剧集模块处理交给默认处理逻辑")
meta.title = re.sub(r'\[.*?\]', '', meta.title).strip()
meta.title = re.sub(r'\[.*?]', '', meta.title).strip()
meta.en_name = meta.title
return meta
split_title: List[str] | None = self.split_season_ep(self.title)
@@ -83,12 +84,13 @@ class ReMeta():
return meta
# 分离季度部分和集数部分
def split_season_ep(self, pre_title: str):
@staticmethod
def split_season_ep(pre_title: str):
split_ep = re.findall(r"(\[.*?])", pre_title)[1]
if not split_ep:
logger.warn("未识别出集数位置信息,结束识别!")
return None
split_title = re.sub(r"\[.*?\]", "", pre_title).strip()
split_title = re.sub(r"\[.*?]", "", pre_title).strip()
logger.info(f"分离出包含季度的部分:{split_title} \n 分离出包含集数的部分: {split_ep}")
return [split_title, split_ep]
@@ -96,7 +98,7 @@ class ReMeta():
if self.high_performance:
with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:
title_season_result = executor.submit(self.handle_season, title[0])
ep_result = executor.submit(self.re_ep, title[1],)
ep_result = executor.submit(self.re_ep, title[1], )
try:
title_season_result = title_season_result.result() # Blocks until the task is complete.
ep_result = ep_result.result() # Blocks until the task is complete.
@@ -140,7 +142,8 @@ class ReMeta():
return title_season
# 处理存在“Final”字样命名的季度
def handle_final_season(self, title: str) -> int | None:
@staticmethod
def handle_final_season(title: str) -> int | None:
medias = MediaChain().search(title=title)[1]
if not medias:
logger.warn("没有找到对应的媒体信息!")
@@ -159,7 +162,7 @@ class ReMeta():
logger.info(f"获取到最终季,季度为{len(seasons_info)}")
return len(seasons_info)
def re_ep(self, ep_title: str,) -> dict:
def re_ep(self, ep_title: str, ) -> dict:
"""
# 集数匹配处理模块
:param ep_title: 从title解析出的集数,ep_title固定格式[集数]
@@ -182,7 +185,3 @@ class ReMeta():
ep_ova["ep"] = int(match.group(group))
return ep_ova
return ep_ova