fix 云盘Strm助手v1.3

移除全量同步、定时同步(谨慎升级)
This commit is contained in:
thsrite
2025-03-28 12:36:03 +08:00
parent 2cc94cec02
commit 9d76d85bf8
3 changed files with 24 additions and 360 deletions

View File

@@ -445,11 +445,12 @@
"name": "云盘Strm助手",
"description": "实时监控、定时全量增量生成strm文件。",
"labels": "云盘",
"version": "1.2.7",
"version": "1.3",
"icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/cloudcompanion.png",
"author": "thsrite",
"level": 1,
"history": {
"v1.3": "移除全量同步、定时同步(谨慎升级)",
"v1.2.7": "增加路径替换规则",
"v1.2.6": "修复交互命令",
"v1.2.5": "可自定义是否UrlEncode编码",

View File

@@ -6,19 +6,12 @@ import threading
import time
import traceback
import urllib.parse
from datetime import datetime, timedelta
from io import BytesIO
from datetime import datetime
from pathlib import Path
from posixpath import join as join_path
from re import compile as re_compile
from typing import Any, List, Dict, Tuple, Optional
from typing import Any, List, Dict, Tuple
import pytz
import requests
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from p115client import P115Client
from posixpatht import escape
from watchdog.events import FileSystemEventHandler
from watchdog.observers.polling import PollingObserver
@@ -63,7 +56,7 @@ class CloudStrmCompanion(_PluginBase):
# 插件图标
plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/cloudcompanion.png"
# 插件版本
plugin_version = "1.2.7"
plugin_version = "1.3"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -77,10 +70,7 @@ class CloudStrmCompanion(_PluginBase):
# 私有属性
_enabled = False
_cron = None
_monitor_confs = None
_onlyonce = False
_rebuild = False
_cover = False
_monitor = False
_copy_files = False
@@ -98,39 +88,29 @@ class CloudStrmCompanion(_PluginBase):
_medias = {}
_rmt_mediaext = None
_other_mediaext = None
_115_cookie = None
_115client = None
_interval: int = 10
_mediaservers = None
mediaserver_helper = None
_emby_paths = {}
_path_replacements = {} # 新增:路径替换规则属性
_path_replacements = {} # 新增:路径替换规则属性
_cloud_files_json = "cloud_files.json"
_headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36",
"Cookie": "",
}
# 定时器
_scheduler: Optional[BackgroundScheduler] = None
# 退出事件
_event = threading.Event()
def init_plugin(self, config: dict = None):
# 清空配置
self._strm_dir_conf = {}
self._cloud_dir_conf = {}
self._format_conf = {}
self._category_conf = {}
self._path_replacements = {} # 新增:清空路径替换规则
self._path_replacements = {} # 新增:清空路径替换规则
self._cloud_files_json = os.path.join(self.get_data_path(), self._cloud_files_json)
self.mediaserver_helper = MediaServerHelper()
if config:
self._enabled = config.get("enabled")
self._cron = config.get("cron")
self._onlyonce = config.get("onlyonce")
self._rebuild = config.get("rebuild")
self._interval = config.get("interval") or 10
self._monitor = config.get("monitor")
self._cover = config.get("cover")
@@ -151,30 +131,14 @@ class CloudStrmCompanion(_PluginBase):
self._path_replacements[source.strip()] = target.strip()
self._rmt_mediaext = config.get(
"rmt_mediaext") or ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
self._115_cookie = config.get("115_cookie")
if self._115_cookie:
self._headers["Cookie"] = self._115_cookie
self._115client = P115Client(self._115_cookie, check_for_relogin=True)
if config.get("emby_path"):
for path in str(config.get("emby_path")).split(","):
self._emby_paths[path.split(":")[0]] = path.split(":")[1]
if self._rebuild:
logger.info("开始清理旧数据索引")
self._rebuild = False
self._cloud_files = []
if Path(self._cloud_files_json).exists():
Path(self._cloud_files_json).unlink()
logger.info("旧数据索引清理完成")
self.__update_config()
# 停止现有任务
self.stop_service()
if self._enabled or self._onlyonce:
# 定时服务
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
if self._enabled:
if self._notify:
# 追加入库消息统一发送服务
self._scheduler.add_job(self.send_msg, trigger='interval', seconds=15)
@@ -238,41 +202,14 @@ class CloudStrmCompanion(_PluginBase):
logger.warn(
f"云盘实时监控服务启动出现异常:{err_msg}请在宿主机上不是docker容器内执行以下命令并重启"
+ """
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
echo fs.inotify.max_user_instances=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
""")
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
echo fs.inotify.max_user_instances=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
""")
else:
logger.error(f"{local_dir} 启动x实时监控失败{err_msg}")
self.systemmessage.put(f"{local_dir} 启动实时监控失败:{err_msg}")
# 运行一次定时服务
if self._onlyonce:
logger.info("云盘Strm助手全量执行服务启动立即运行一次")
self._scheduler.add_job(func=self.scan, trigger='date',
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3),
name="云盘Strm助手全量执行")
# 关闭一次性开关
self._onlyonce = False
# 保存配置
self.__update_config()
# 周期运行
if self._cron:
try:
self._scheduler.add_job(func=self.scan,
trigger=CronTrigger.from_crontab(self._cron),
name="云盘Strm助手同步")
except Exception as err:
logger.error(f"定时任务配置错误:{err}")
# 推送实时消息
self.systemmessage.put(f"执行周期配置错误:{err}")
# 启动任务
if self._scheduler.get_jobs():
self._scheduler.print_jobs()
self._scheduler.start()
@eventmanager.register(EventType.PluginAction)
def strm_one(self, event: Event = None):
if event:
@@ -298,112 +235,6 @@ class CloudStrmCompanion(_PluginBase):
# 处理单文件
self.__handle_file(event_path=file_path, mon_path=mon_path)
@eventmanager.register(EventType.PluginAction)
def scan(self, event: Event = None):
"""
扫描
"""
if not self._strm_dir_conf or not self._strm_dir_conf.keys():
logger.error("未获取到可用目录监控配置,请检查")
return
if event:
event_data = event.event_data
if not event_data or event_data.get("action") != "CloudStrmCompanion":
return
logger.info("收到命令开始云盘Strm助手同步生成 ...")
self.post_message(channel=event.event_data.get("channel"),
title="开始云盘Strm助手同步生成 ...",
userid=event.event_data.get("user"))
if not self._115client:
logger.error("115_cookie 未配置或cookie已失效请检查配置")
return
logger.info("云盘Strm助手同步生成任务开始")
# 首次扫描或者重建索引
if Path(self._cloud_files_json).exists():
logger.info("尝试加载本地缓存")
# 尝试加载本地
with open(self._cloud_files_json, 'r') as file:
content = file.read()
if content:
self._cloud_files = json.loads(content)
__save_flag = False
# 遍历云盘目录
for local_dir in self._cloud_dir_conf.keys():
# 云盘路径
cloud_dir = self._cloud_dir_conf.get(local_dir)
# 本地strm路径
strm_dir = self._strm_dir_conf.get(local_dir)
# 格式化配置
format_str = self._format_conf.get(local_dir)
# 获取云盘树形结构
tree_content = self.retrieve_directory_structure(cloud_dir)
if not tree_content:
continue
# 遍历云盘树形结构文件
for cloud_file in self.parse_tree_structure(content=tree_content, dir_path=cloud_dir):
if Path(str(cloud_file)).is_dir():
continue
# 本地挂载路径
local_file = str(cloud_file).replace(cloud_dir, local_dir)
# 本地strm路径
target_file = str(cloud_file).replace(cloud_dir, strm_dir)
success_flag = False
try:
if str(cloud_file) not in self._cloud_files:
logger.info(f"扫描到新文件 {cloud_file},正在开始处理")
# 只处理媒体文件
if Path(local_file).suffix.lower() in [ext.strip() for ext in
self._rmt_mediaext.split(",")]:
# 生成strm文件内容
strm_content = self.__format_content(format_str=format_str,
local_file=local_file,
cloud_file=str(cloud_file),
uriencode=self._uriencode)
# 生成strm文件
success_flag = self.__create_strm_file(strm_file=target_file,
strm_content=strm_content)
else:
# 复制非媒体文件
if self._copy_files and self._other_mediaext and Path(local_file).suffix.lower() in [
ext.strip() for ext in self._other_mediaext.split(",")]:
os.makedirs(os.path.dirname(target_file), exist_ok=True)
shutil.copy2(str(local_file), target_file)
logger.info(f"复制非媒体文件 {str(local_file)}{target_file}")
# 复制字幕文件独立于copy_files检查
if self._copy_subtitles and Path(local_file).suffix.lower() in ['.srt', '.ass', '.ssa',
'.sub']:
os.makedirs(os.path.dirname(target_file), exist_ok=True)
shutil.copy2(str(local_file), target_file)
logger.info(f"复制字幕文件 {str(local_file)}{target_file}")
success_flag = True
else:
logger.info(f"{cloud_file} 已在缓存中!跳过处理")
except Exception as e:
logger.error(f"处理文件 {cloud_file} 失败:{str(e)}")
success_flag = False
if success_flag:
# 云盘文件json新增
self._cloud_files.append(str(cloud_file))
__save_flag = True
# 重新保存json文件
if __save_flag:
self.__sava_json()
logger.info("云盘Strm助手同步生成任务完成")
if event:
self.post_message(channel=event.event_data.get("channel"),
title="云盘Strm助手同步生成任务完成",
userid=event.event_data.get("user"))
def event_handler(self, event, mon_path: str, text: str, event_path: str):
"""
处理文件变化
@@ -662,85 +493,6 @@ class CloudStrmCompanion(_PluginBase):
time.sleep(3)
return None
def retrieve_directory_structure(self, directory_path):
"""
获取目录树结构
"""
file_id = None
try:
logger.info(f"开始生成 {directory_path} 目录树")
dir_info = self._115client.fs_dir_getid(directory_path)
if not dir_info:
logger.error(f"{directory_path} 目录不存在或路径错误")
return
fid = dir_info.get("id")
if not fid:
logger.error(f"{directory_path} 目录不存在或路径错误")
return
pick_code, file_id = self.export_dir(fid)
if not pick_code:
logger.error(f"{directory_path} 生成目录数失败")
return
# 获取目录树下载链接
download_url = self._115client.download_url(pick_code, headers=self._headers)
directory_content = self.fetch_content(download_url)
logger.info(f"{directory_path} 目录树下载成功")
return directory_content
except Exception as e:
logger.error(f"{directory_path} 目录树生成失败: {str(e)}")
finally:
if file_id:
try:
self._115client.fs_delete(file_id)
except:
pass
def fetch_content(self, url):
"""
下载目录树文件内容
"""
try:
with requests.get(url, headers=self._headers, stream=True, timeout=60) as response:
response.raise_for_status()
content = BytesIO()
for chunk in response.iter_content(chunk_size=8192):
content.write(chunk)
return content.getvalue().decode("utf-16")
except:
logger.error(f"文件下载失败: {traceback.format_exc()}")
return None
@staticmethod
def parse_tree_structure(content: str, dir_path: str):
"""
解析目录树内容并生成每个路径
"""
tree_pattern = re_compile(r"^(?:\| )+\|-")
dir_path = Path(dir_path)
current_path = [str(dir_path.parent)] if dir_path.parent != Path("/") or (dir_path.parent == dir_path and (
dir_path.is_absolute() or ':' in dir_path.name)) else ["/"] # 初始化当前路径为根目录
for line in content.splitlines():
# 匹配目录树的每一行
match = tree_pattern.match(line)
if not match:
continue # 跳过不符合格式的行
# 计算当前行的深度
level_indicator = match.group(0)
depth = (len(level_indicator) // 2) - 1
# 获取当前行的目录名称,去掉前面的 '| ' 或 '- '
item_name = escape(line.strip()[len(level_indicator):].strip())
# 根据深度更新当前路径
if depth < len(current_path):
current_path[depth] = item_name # 更新已有深度的名称
else:
current_path.append(item_name) # 添加新的深度名称
# 生成并返回当前深度的完整路径
yield join_path(*current_path[:depth + 1]).replace('\\', '/')
@eventmanager.register(EventType.PluginAction)
def remote_sync_one(self, event: Event = None):
if event:
@@ -1007,7 +759,8 @@ class CloudStrmCompanion(_PluginBase):
"other_mediaext": self._other_mediaext,
"mediaservers": self._mediaservers,
# 新增:路径替换规则
"path_replacements": "\n".join([f"{source}:{target}" for source, target in self._path_replacements.items()]) if self._path_replacements else "",
"path_replacements": "\n".join([f"{source}:{target}" for source, target in
self._path_replacements.items()]) if self._path_replacements else "",
})
def get_state(self) -> bool:
@@ -1051,14 +804,6 @@ class CloudStrmCompanion(_PluginBase):
"kwargs": {} # 定时器参数
}]
"""
if self._enabled and self._cron:
return [{
"id": "CloudStrmCompanion",
"name": "云盘Strm助手同步",
"trigger": CronTrigger.from_crontab(self._cron),
"func": self.scan,
"kwargs": {}
}]
return []
def get_api(self) -> List[Dict[str, Any]]:
@@ -1159,24 +904,8 @@ class CloudStrmCompanion(_PluginBase):
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '全量同步一次',
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'rebuild',
'label': '重建缓存',
'model': 'notify',
'label': '发送通知',
}
}
]
@@ -1196,12 +925,7 @@ class CloudStrmCompanion(_PluginBase):
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
},
{
'component': 'VCol',
'props': {
@@ -1212,12 +936,17 @@ class CloudStrmCompanion(_PluginBase):
{
'component': 'VSwitch',
'props': {
'model': 'notify',
'label': '发送通知',
'model': 'uriencode',
'label': 'url编码',
}
}
]
},
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
@@ -1255,60 +984,6 @@ class CloudStrmCompanion(_PluginBase):
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'uriencode',
'label': 'url编码',
}
}
]
},
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VCronField',
'props': {
'model': 'cron',
'label': '同步周期',
'placeholder': '0 0 * * *'
}
}
]
},
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 4
},
'content': [
{
'component': 'VTextField',
'props': {
'model': '115_cookie',
'label': '115Cookie',
}
}
]
},
{
'component': 'VCol',
'props': {
@@ -1536,9 +1211,6 @@ class CloudStrmCompanion(_PluginBase):
}
], {
"enabled": False,
"cron": "",
"onlyonce": False,
"rebuild": False,
"notify": False,
"monitor": False,
"cover": False,
@@ -1550,7 +1222,6 @@ class CloudStrmCompanion(_PluginBase):
"monitor_confs": "",
"emby_path": "",
"interval": 10,
"115_cookie": "",
"url": "",
"other_mediaext": ".nfo, .jpg, .png, .json",
"rmt_mediaext": ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v",
@@ -1572,10 +1243,3 @@ class CloudStrmCompanion(_PluginBase):
except Exception as e:
print(str(e))
self._observer = []
if self._scheduler:
self._scheduler.remove_all_jobs()
if self._scheduler.running:
self._event.set()
self._scheduler.shutdown()
self._event.clear()
self._scheduler = None

View File

@@ -1 +0,0 @@
python-115