diff --git a/app/api/endpoints/plugin.py b/app/api/endpoints/plugin.py index 25666fa3..e34bea5b 100644 --- a/app/api/endpoints/plugin.py +++ b/app/api/endpoints/plugin.py @@ -146,11 +146,32 @@ def all_plugins(_: schemas.TokenPayload = Depends(get_current_active_superuser), installed_plugins = [plugin for plugin in local_plugins if plugin.installed] # 未安装的本地插件 not_installed_plugins = [plugin for plugin in local_plugins if not plugin.installed] - if state == "installed": - return installed_plugins # 在线插件 online_plugins = PluginManager().get_online_plugins() + + # 为已安装插件补充标签信息 + if online_plugins: + # 创建在线插件的ID到插件对象的映射 + online_plugin_map = {plugin.id: plugin for plugin in online_plugins} + + # 为已安装插件补充标签信息 + updated_installed_plugins = [] + for installed_plugin in installed_plugins: + if installed_plugin.id in online_plugin_map: + online_plugin = online_plugin_map[installed_plugin.id] + # 如果已安装插件没有标签但在线插件有标签,则补充标签信息 + if not installed_plugin.plugin_label and online_plugin.plugin_label: + # 直接更新原对象的标签字段,避免创建新对象可能导致的状态丢失 + installed_plugin.plugin_label = online_plugin.plugin_label + + updated_installed_plugins.append(installed_plugin) + + installed_plugins = updated_installed_plugins + + if state == "installed": + return installed_plugins + if not online_plugins: # 没有获取在线插件 if state == "market": @@ -374,6 +395,75 @@ def plugin_static_file(plugin_id: str, filepath: str): raise HTTPException(status_code=500, detail="Internal Server Error") +@router.get("/folders", summary="获取插件文件夹配置", response_model=dict) +def get_plugin_folders(_: schemas.TokenPayload = Depends(get_current_active_superuser)) -> dict: + """ + 获取插件文件夹分组配置 + """ + try: + result = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {} + return result + except Exception as e: + logger.error(f"[文件夹API] 获取文件夹配置失败: {str(e)}") + return {} + + +@router.post("/folders", summary="保存插件文件夹配置", response_model=schemas.Response) +def save_plugin_folders(folders: dict, _: schemas.TokenPayload = Depends(get_current_active_superuser)) -> Any: + """ + 保存插件文件夹分组配置 + """ + try: + SystemConfigOper().set(SystemConfigKey.PluginFolders, folders) + + # 验证保存结果 + saved_result = SystemConfigOper().get(SystemConfigKey.PluginFolders) + + return schemas.Response(success=True) + except Exception as e: + logger.error(f"[文件夹API] 保存文件夹配置失败: {str(e)}") + return schemas.Response(success=False, message=str(e)) + + +@router.post("/folders/{folder_name}", summary="创建插件文件夹", response_model=schemas.Response) +def create_plugin_folder(folder_name: str, _: schemas.TokenPayload = Depends(get_current_active_superuser)) -> Any: + """ + 创建新的插件文件夹 + """ + folders = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {} + if folder_name not in folders: + folders[folder_name] = [] + SystemConfigOper().set(SystemConfigKey.PluginFolders, folders) + return schemas.Response(success=True, message=f"文件夹 '{folder_name}' 创建成功") + else: + return schemas.Response(success=False, message=f"文件夹 '{folder_name}' 已存在") + + +@router.delete("/folders/{folder_name}", summary="删除插件文件夹", response_model=schemas.Response) +def delete_plugin_folder(folder_name: str, _: schemas.TokenPayload = Depends(get_current_active_superuser)) -> Any: + """ + 删除插件文件夹 + """ + folders = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {} + if folder_name in folders: + del folders[folder_name] + SystemConfigOper().set(SystemConfigKey.PluginFolders, folders) + return schemas.Response(success=True, message=f"文件夹 '{folder_name}' 删除成功") + else: + return schemas.Response(success=False, message=f"文件夹 '{folder_name}' 不存在") + + +@router.put("/folders/{folder_name}/plugins", summary="更新文件夹中的插件", response_model=schemas.Response) +def update_folder_plugins(folder_name: str, plugin_ids: List[str], _: schemas.TokenPayload = Depends(get_current_active_superuser)) -> Any: + """ + 更新指定文件夹中的插件列表 + """ + folders = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {} + folders[folder_name] = plugin_ids + SystemConfigOper().set(SystemConfigKey.PluginFolders, folders) + return schemas.Response(success=True, message=f"文件夹 '{folder_name}' 中的插件已更新") + + @router.get("/{plugin_id}", summary="获取插件配置") def plugin_config(plugin_id: str, _: schemas.TokenPayload = Depends(get_current_active_superuser)) -> dict: diff --git a/app/schemas/types.py b/app/schemas/types.py index 116bb13f..91c1bd1a 100644 --- a/app/schemas/types.py +++ b/app/schemas/types.py @@ -141,6 +141,8 @@ class SystemConfigKey(Enum): UserInstalledPlugins = "UserInstalledPlugins" # 插件安装统计 PluginInstallReport = "PluginInstallReport" + # 插件文件夹分组配置 + PluginFolders = "PluginFolders" # 默认电影订阅规则 DefaultMovieSubscribeConfig = "DefaultMovieSubscribeConfig" # 默认电视剧订阅规则 diff --git a/config/app.env b/config/app.env deleted file mode 100644 index 4e04ec29..00000000 --- a/config/app.env +++ /dev/null @@ -1,25 +0,0 @@ -####################################################################################################### -# V2版本中大部分设置可通过后台设置界面进行配置,本文件仅展示界面无法配置的项, 这些项同样可以通过环境变量进行设置 # -####################################################################################################### -# 【*】API监听地址(注意不是前端访问地址) -HOST=0.0.0.0 -# 【*】超级管理员,设置后一但重启将固化到数据库中,修改将无效(初始化超级管理员密码仅会生成一次,请在日志中查看并自行登录系统修改) -SUPERUSER=admin -# 自动检查和更新站点资源包(索引、认证等) -AUTO_UPDATE_RESOURCE=true -# 媒体识别来源 themoviedb/douban,使用themoviedb时需要确保能正常连接api.themoviedb.org,使用douban时不支持二级分类 -RECOGNIZE_SOURCE=themoviedb -# OCR服务器地址 -OCR_HOST=https://movie-pilot.org -# 搜索多个名称,true/false,为true时搜索时会同时搜索中英文及原始名称,搜索结果会更全面,但会增加搜索时间;为false时其中一个名称搜索到结果或全部名称搜索完毕即停止 -SEARCH_MULTIPLE_NAME=false -# 为指定字幕添加.default后缀设置为默认字幕,支持为'zh-cn','zh-tw','eng'添加默认字幕,未定义或设置为None则不添加 -DEFAULT_SUB=zh-cn -# 数据库连接池的大小,可适当降低如20-50以减少I/O压力 -DB_POOL_SIZE=100 -# 数据库连接池最大溢出连接数,可适当降低如0以减少I/O压力 -DB_MAX_OVERFLOW=500 -# SQLite 的 busy_timeout 参数,可适当增加如180以减少锁定错误 -DB_TIMEOUT=60 -# 是否开发调试模式,仅开发人员使用,打开后将停止后台服务 -DEV=false diff --git a/requirements.in b/requirements.in deleted file mode 100644 index 099bfd1d..00000000 --- a/requirements.in +++ /dev/null @@ -1,72 +0,0 @@ -Cython~=3.0.12 -pydantic~=1.10.13 -SQLAlchemy~=2.0.15 -uvicorn~=0.22.0 -fastapi~=0.96.0 -passlib~=1.7.4 -PyJWT~=2.7.0 -python-multipart~=0.0.9 -alembic~=1.11.1 -bcrypt~=4.0.1 -regex~=2023.6.3 -cn2an~=0.5.19 -dateparser~=1.1.8 -python-dateutil~=2.8.2 -zhconv~=1.4.3 -anitopy~=2.1.1 -requests[socks]~=2.32.3 -urllib3~=2.2.2 -lxml~=4.9.2 -pyquery~=2.0.0 -ruamel.yaml~=0.17.31 -APScheduler~=3.10.1 -cryptography~=43.0.0 -pytz~=2023.3 -pycryptodome~=3.20.0 -qbittorrent-api==2024.11.70 -plexapi~=4.16.0 -transmission-rpc~=4.3.0 -Jinja2~=3.1.4 -pyparsing~=3.0.9 -func_timeout==4.3.5 -bs4~=0.0.1 -beautifulsoup4~=4.12.2 -pillow~=10.4.0 -pillow-avif-plugin~=1.4.6 -pyTelegramBotAPI~=4.12.0 -playwright~=1.49.1 -cf-clearance~=0.31.0 -torrentool~=1.2.0 -slack-bolt~=1.18.0 -slack-sdk~=3.21.3 -chardet~=4.0.0 -starlette~=0.27.0 -PyVirtualDisplay~=3.0 -psutil~=5.9.4 -python-dotenv~=1.0.1 -python-hosts~=1.0.7 -watchdog~=3.0.0 -openai~=0.27.2 -cacheout~=0.14.1 -click~=8.1.6 -requests-cache~=0.5.2 -parse~=1.19.0 -docker~=7.1.0 -pywin32==306; platform_system == "Windows" -cachetools~=5.3.1 -fast-bencode~=1.1.3 -pystray~=0.19.5 -pyotp~=2.9.0 -Pinyin2Hanzi~=0.1.1 -pywebpush~=2.0.0 -python-cookietools==0.0.2.1 -aiofiles~=24.1.0 -jieba~=0.42.1 -rsa~=4.9 -redis~=5.2.1 -async_timeout~=5.0.1; python_full_version < "3.11.3" -packaging~=24.2 -cf_clearance~=0.31.0 -oss2~=2.19.1 -tqdm~=4.67.1 -setuptools~=78.1.0