{ "swagger": "2.0", "info": { "description": "轻量级音乐服务器 API 文档,支持本地音乐管理、网络歌曲、电台和歌单功能", "title": "Songloft API", "contact": { "name": "API Support", "url": "https://github.com/songloft-org/songloft", "email": "im.hanxi@gmail.com" }, "license": { "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0" }, "version": "2.11.0" }, "host": "localhost:58091", "basePath": "/api/v1", "paths": { "/api/v1/jsplugin-assets/{path}": { "get": { "description": "服务由主程序嵌入的插件通用 CSS、JS 和字体文件,自动注入到所有插件 HTML 页面。", "produces": [ "application/octet-stream" ], "tags": [ "JS 插件" ], "summary": "插件公共资源", "parameters": [ { "type": "string", "description": "资源路径", "name": "*", "in": "path", "required": true } ], "responses": { "200": { "description": "资源文件" }, "404": { "description": "资源不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/api/v1/songs/organize": { "post": { "security": [ { "BearerAuth": [] } ], "description": "批量移动/重命名本地歌曲文件到指定目录结构。target_path 为相对于 music_path 的路径(含目录和文件名),扩展名必须与原文件一致。CUE 拆分歌曲会被跳过(status=skip);目标文件已存在时拒绝覆盖(status=error)。music_path 由服务端自取。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "批量整理歌曲文件", "parameters": [ { "description": "整理项目列表", "name": "request", "in": "body", "required": true, "schema": { "type": "array", "items": { "$ref": "#/definitions/services.OrganizeItem" } } } ], "responses": { "200": { "description": "整理结果", "schema": { "type": "array", "items": { "$ref": "#/definitions/services.OrganizeResult" } } }, "400": { "description": "请求错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/api/v1/songs/organize/preview": { "post": { "security": [ { "BearerAuth": [] } ], "description": "dry-run 预览目录整理变更,返回每项 old_path→new_path 与状态(ok/conflict/skip/error),不移动任何文件、不改数据库。target_path 为相对 music_path 的路径。CUE 歌曲 skip;目标已存在或批内撞名 conflict。music_path 由服务端自取。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "预览批量整理歌曲文件", "parameters": [ { "description": "整理项目列表", "name": "request", "in": "body", "required": true, "schema": { "type": "array", "items": { "$ref": "#/definitions/services.OrganizeItem" } } } ], "responses": { "200": { "description": "预览结果", "schema": { "type": "array", "items": { "$ref": "#/definitions/services.OrganizePreviewResult" } } }, "400": { "description": "请求错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/api/v1/songs/{id}/tags": { "put": { "security": [ { "BearerAuth": [] } ], "description": "将元数据写入数据库和本地音频文件标签(仅本地歌曲)。cover_data(base64) 优先于 cover_url。非空字段覆盖,空值保留原值。设置 clear_cover=true 可显式清空封面。rename_file=true 时按新标题重命名本地音频文件(保留原目录与扩展名,仅本地非 CUE 歌曲生效);标题清理后为空或目标文件名已存在时返回 400,与原文件同名则不移动仅写库。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "写入歌曲标签", "parameters": [ { "type": "integer", "description": "歌曲ID", "name": "id", "in": "path", "required": true }, { "description": "标签数据", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.WriteSongTagsRequest" } } ], "responses": { "200": { "description": "写入结果", "schema": { "type": "object", "properties": { "file_write": { "type": "string" }, "song": { "$ref": "#/definitions/models.Song" } } } }, "400": { "description": "请求错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/auth/login": { "post": { "description": "用户登录获取访问令牌", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "认证管理" ], "summary": "用户登录", "parameters": [ { "description": "登录请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.LoginRequest" } } ], "responses": { "200": { "description": "登录成功", "schema": { "$ref": "#/definitions/models.LoginResponse" } }, "400": { "description": "请求数据错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "401": { "description": "用户名或密码错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/auth/logout": { "post": { "security": [ { "BearerAuth": [] } ], "description": "用户登出,撤销当前访问令牌", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "认证管理" ], "summary": "用户登出", "responses": { "200": { "description": "登出成功", "schema": { "$ref": "#/definitions/models.SuccessResponse" } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/auth/refresh": { "post": { "description": "使用刷新令牌获取新的访问令牌", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "认证管理" ], "summary": "刷新令牌", "parameters": [ { "description": "刷新令牌请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.RefreshTokenRequest" } } ], "responses": { "200": { "description": "刷新成功", "schema": { "$ref": "#/definitions/services.RefreshResponse" } }, "400": { "description": "请求数据错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "401": { "description": "刷新令牌无效", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/auth/tokens": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取当前用户的所有活跃令牌列表", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "认证管理" ], "summary": "列出活跃令牌", "parameters": [ { "enum": [ "access", "refresh" ], "type": "string", "description": "令牌类型", "name": "type", "in": "query" }, { "type": "integer", "default": 20, "description": "每页数量", "name": "limit", "in": "query" }, { "type": "integer", "default": 0, "description": "偏移量", "name": "offset", "in": "query" } ], "responses": { "200": { "description": "令牌列表", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/auth/tokens/{token_id}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取指定令牌的详细信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "认证管理" ], "summary": "获取令牌信息", "parameters": [ { "type": "string", "description": "令牌ID", "name": "token_id", "in": "path", "required": true } ], "responses": { "200": { "description": "令牌信息", "schema": { "$ref": "#/definitions/models.TokenInfo" } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "令牌不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } }, "delete": { "security": [ { "BearerAuth": [] } ], "description": "撤销指定的令牌", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "认证管理" ], "summary": "撤销令牌", "parameters": [ { "type": "string", "description": "令牌ID", "name": "token_id", "in": "path", "required": true }, { "description": "撤销令牌请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.RevokeTokenRequest" } } ], "responses": { "200": { "description": "撤销成功", "schema": { "$ref": "#/definitions/models.SuccessResponse" } }, "400": { "description": "请求数据错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/cache-manage/clean": { "post": { "security": [ { "BearerAuth": [] } ], "description": "删除服务端所有已缓存的音乐文件,清理后需要重新下载", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "缓存管理" ], "summary": "清理全部音乐缓存", "responses": { "200": { "description": "清理成功", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "清理失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/cache-manage/config": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取服务端音乐缓存的配置信息,包括最大缓存大小限制、缓存目录路径、缓存转码格式(transcode_format)与码率(transcode_quality)。cache_dir 为空表示使用 default_cache_dir;transcode_format 为空表示缓存不转码、按原格式落盘。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "缓存管理" ], "summary": "获取缓存配置", "responses": { "200": { "description": "缓存配置", "schema": { "$ref": "#/definitions/services.CacheConfigResponse" } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "更新服务端音乐缓存的配置,如最大缓存大小和缓存目录。cache_dir 为空字符串时恢复使用默认目录。transcode_format 非空(mp3/m4a/ogg/flac/wav)时,缓存网络歌曲落盘会统一转码为该格式(缺 ffmpeg 或转码失败时保留原格式),transcode_quality 为可选码率(128/192/320,空或非法为最高质量)。更新后会自动触发 LRU 淘汰检查。切换目录时不会自动迁移旧缓存文件。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "缓存管理" ], "summary": "更新缓存配置", "parameters": [ { "description": "缓存配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/services.CacheConfig" } } ], "responses": { "200": { "description": "更新后的缓存配置", "schema": { "$ref": "#/definitions/services.CacheConfigResponse" } }, "400": { "description": "请求参数无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "更新失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/cache-manage/stats": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取服务端音乐缓存的统计信息,包括总大小、文件数量和最大缓存限制", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "缓存管理" ], "summary": "获取缓存统计信息", "responses": { "200": { "description": "缓存统计信息", "schema": { "$ref": "#/definitions/services.CacheStats" } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/cache-manage/validate-dir": { "post": { "security": [ { "BearerAuth": [] } ], "description": "验证指定目录是否可用作缓存目录。目录不存在时自动创建,检查可写性并返回磁盘空间信息。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "缓存管理" ], "summary": "验证缓存目录", "parameters": [ { "description": "目录路径", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.dirValidateRequest" } } ], "responses": { "200": { "description": "验证结果", "schema": { "$ref": "#/definitions/handlers.dirValidateResponse" } }, "400": { "description": "请求参数无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/configs": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取配置列表,支持关键词搜索和分页", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "配置管理" ], "summary": "获取配置列表", "parameters": [ { "type": "string", "description": "搜索关键词", "name": "keyword", "in": "query" }, { "type": "integer", "default": 20, "description": "每页数量", "name": "limit", "in": "query" }, { "type": "integer", "default": 0, "description": "偏移量", "name": "offset", "in": "query" } ], "responses": { "200": { "description": "成功返回配置列表", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "post": { "security": [ { "BearerAuth": [] } ], "description": "创建一个新的配置", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "配置管理" ], "summary": "创建配置", "parameters": [ { "description": "配置信息", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.CreateConfigRequest" } } ], "responses": { "201": { "description": "创建成功", "schema": { "$ref": "#/definitions/models.Config" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "创建失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/configs/{key}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "根据配置键获取详细信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "配置管理" ], "summary": "获取单个配置详情", "parameters": [ { "type": "string", "description": "配置键", "name": "key", "in": "path", "required": true } ], "responses": { "200": { "description": "成功返回配置详情", "schema": { "$ref": "#/definitions/models.Config" } }, "404": { "description": "配置不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "更新配置信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "配置管理" ], "summary": "更新配置", "parameters": [ { "type": "string", "description": "配置键", "name": "key", "in": "path", "required": true }, { "description": "配置信息", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.UpdateConfigRequest" } } ], "responses": { "200": { "description": "更新成功", "schema": { "$ref": "#/definitions/models.Config" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "配置不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "delete": { "security": [ { "BearerAuth": [] } ], "description": "根据配置键删除配置", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "配置管理" ], "summary": "删除配置", "parameters": [ { "type": "string", "description": "配置键", "name": "key", "in": "path", "required": true } ], "responses": { "200": { "description": "删除成功", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "400": { "description": "无效的配置键", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "删除失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/health": { "get": { "description": "检查应用是否正常运行", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统管理" ], "summary": "检查应用健康状态", "responses": { "200": { "description": "成功返回健康状态", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/jsplugin/{entryPath}": { "get": { "description": "JS 插件入口 HTML。{entryPath} 由运行时按已安装插件决定,注入 \u003cbase\u003e 标签和 auth-bridge 脚本后返回 static/index.html。无需认证。", "produces": [ "text/html" ], "tags": [ "JS 插件" ], "summary": "插件根页面(动态路由)", "parameters": [ { "type": "string", "description": "插件入口标识(运行时动态)", "name": "entryPath", "in": "path", "required": true } ], "responses": { "200": { "description": "插件 index.html(已注入 \u003cbase\u003e 与 auth-bridge)", "schema": { "type": "string" } }, "404": { "description": "插件未安装或缺 static/index.html", "schema": { "type": "string" } } } } }, "/jsplugin/{entryPath}/files/{path}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "通过 Go 原生 http.ServeFile 直接返回插件可访问范围内的文件,支持 Range 请求和 HTTP 缓存。", "produces": [ "application/octet-stream" ], "tags": [ "JS 插件" ], "summary": "插件文件直接访问", "parameters": [ { "type": "string", "description": "插件入口标识", "name": "entryPath", "in": "path", "required": true }, { "type": "string", "description": "文件路径", "name": "path", "in": "path", "required": true } ], "responses": { "200": { "description": "文件内容", "schema": { "type": "file" } }, "206": { "description": "部分文件内容(Range 请求)", "schema": { "type": "file" } }, "404": { "description": "文件不存在或权限不足", "schema": { "type": "string" } } } }, "head": { "security": [ { "BearerAuth": [] } ], "description": "通过 Go 原生 http.ServeFile 直接返回插件可访问范围内的文件,支持 Range 请求和 HTTP 缓存。", "produces": [ "application/octet-stream" ], "tags": [ "JS 插件" ], "summary": "插件文件直接访问", "parameters": [ { "type": "string", "description": "插件入口标识", "name": "entryPath", "in": "path", "required": true }, { "type": "string", "description": "文件路径", "name": "path", "in": "path", "required": true } ], "responses": { "200": { "description": "文件内容", "schema": { "type": "file" } }, "206": { "description": "部分文件内容(Range 请求)", "schema": { "type": "file" } }, "404": { "description": "文件不存在或权限不足", "schema": { "type": "string" } } } } }, "/jsplugin/{entryPath}/static": { "get": { "description": "服务 static/index.html,是 handlePluginStatic 的「带 /static 后缀」变体。无需认证。", "produces": [ "text/html" ], "tags": [ "JS 插件" ], "summary": "插件 static 目录根(动态路由)", "parameters": [ { "type": "string", "description": "插件入口标识(运行时动态)", "name": "entryPath", "in": "path", "required": true } ], "responses": { "200": { "description": "插件 index.html", "schema": { "type": "string" } }, "404": { "description": "插件未安装或缺 static/index.html", "schema": { "type": "string" } } } } }, "/jsplugin/{entryPath}/static/{path}": { "get": { "description": "从插件磁盘目录返回 CSS/JS/图片等静态资源;未命中且非 index.html 时 SPA fallback 到 index.html。无需认证。", "produces": [ "application/octet-stream" ], "tags": [ "JS 插件" ], "summary": "插件静态资源文件(动态路由)", "parameters": [ { "type": "string", "description": "插件入口标识(运行时动态)", "name": "entryPath", "in": "path", "required": true } ], "responses": { "200": { "description": "静态资源文件内容", "schema": { "type": "file" } }, "404": { "description": "文件不存在且无 index.html fallback", "schema": { "type": "string" } } } } }, "/jsplugin/{entryPath}/{path}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "接受任意 HTTP 方法,分发到插件 static 兜底、入站 WebSocket upgrade(调用 onWebSocket),或转发到 QuickJS 沙盒中的插件代码。{entryPath} 和子路径均由运行时决定,OpenAPI 仅作占位。需要 BearerAuth。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS 插件" ], "summary": "插件 API 转发 catch-all(动态路由)", "parameters": [ { "type": "string", "description": "插件入口标识(运行时动态)", "name": "entryPath", "in": "path", "required": true }, { "type": "integer", "description": "可选:放宽本次插件调用的超时(毫秒),宿主 clamp 到 [30000, 300000]。慢端点(如 yt-dlp 展开歌单)用于避免默认 30s 超时", "name": "X-Plugin-Timeout-Ms", "in": "header" } ], "responses": { "200": { "description": "插件自定义响应", "schema": { "type": "object", "additionalProperties": true } }, "403": { "description": "插件未启用", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "插件不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "503": { "description": "插件不可用或运行异常(健康检查会自愈)", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "504": { "description": "JS 运行时调用超时", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "接受任意 HTTP 方法,分发到插件 static 兜底、入站 WebSocket upgrade(调用 onWebSocket),或转发到 QuickJS 沙盒中的插件代码。{entryPath} 和子路径均由运行时决定,OpenAPI 仅作占位。需要 BearerAuth。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS 插件" ], "summary": "插件 API 转发 catch-all(动态路由)", "parameters": [ { "type": "string", "description": "插件入口标识(运行时动态)", "name": "entryPath", "in": "path", "required": true }, { "type": "integer", "description": "可选:放宽本次插件调用的超时(毫秒),宿主 clamp 到 [30000, 300000]。慢端点(如 yt-dlp 展开歌单)用于避免默认 30s 超时", "name": "X-Plugin-Timeout-Ms", "in": "header" } ], "responses": { "200": { "description": "插件自定义响应", "schema": { "type": "object", "additionalProperties": true } }, "403": { "description": "插件未启用", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "插件不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "503": { "description": "插件不可用或运行异常(健康检查会自愈)", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "504": { "description": "JS 运行时调用超时", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "post": { "security": [ { "BearerAuth": [] } ], "description": "接受任意 HTTP 方法,分发到插件 static 兜底、入站 WebSocket upgrade(调用 onWebSocket),或转发到 QuickJS 沙盒中的插件代码。{entryPath} 和子路径均由运行时决定,OpenAPI 仅作占位。需要 BearerAuth。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS 插件" ], "summary": "插件 API 转发 catch-all(动态路由)", "parameters": [ { "type": "string", "description": "插件入口标识(运行时动态)", "name": "entryPath", "in": "path", "required": true }, { "type": "integer", "description": "可选:放宽本次插件调用的超时(毫秒),宿主 clamp 到 [30000, 300000]。慢端点(如 yt-dlp 展开歌单)用于避免默认 30s 超时", "name": "X-Plugin-Timeout-Ms", "in": "header" } ], "responses": { "200": { "description": "插件自定义响应", "schema": { "type": "object", "additionalProperties": true } }, "403": { "description": "插件未启用", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "插件不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "503": { "description": "插件不可用或运行异常(健康检查会自愈)", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "504": { "description": "JS 运行时调用超时", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "delete": { "security": [ { "BearerAuth": [] } ], "description": "接受任意 HTTP 方法,分发到插件 static 兜底、入站 WebSocket upgrade(调用 onWebSocket),或转发到 QuickJS 沙盒中的插件代码。{entryPath} 和子路径均由运行时决定,OpenAPI 仅作占位。需要 BearerAuth。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS 插件" ], "summary": "插件 API 转发 catch-all(动态路由)", "parameters": [ { "type": "string", "description": "插件入口标识(运行时动态)", "name": "entryPath", "in": "path", "required": true }, { "type": "integer", "description": "可选:放宽本次插件调用的超时(毫秒),宿主 clamp 到 [30000, 300000]。慢端点(如 yt-dlp 展开歌单)用于避免默认 30s 超时", "name": "X-Plugin-Timeout-Ms", "in": "header" } ], "responses": { "200": { "description": "插件自定义响应", "schema": { "type": "object", "additionalProperties": true } }, "403": { "description": "插件未启用", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "插件不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "503": { "description": "插件不可用或运行异常(健康检查会自愈)", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "504": { "description": "JS 运行时调用超时", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/jsplugins": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取 JS 插件列表", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "列出所有 JS 插件", "responses": { "200": { "description": "JS插件列表", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/registry/install": { "post": { "security": [ { "BearerAuth": [] } ], "description": "从注册表中的 download_url 下载 ZIP 并安装插件。如果 entry_path 已存在且属于同一作者,则自动走更新路径。支持 GitHub 代理(含 api.github.com 私有仓库 Release 资源的下载,代理端需开启 FORWARD_AUTHORIZATION_API 才会转发 token)。可选传入 token 字段用于从需要认证的私有源下载;若未提供 token 但提供了 source_url(「全部」聚合模式),后端会自动从 plugin_registries 配置解析该源存储的 token。\n若 entry_path 已被本地一个**不同作者**的插件占用,返回 409 且不做任何写入(不落盘、不动 static 目录、不改数据库)。前端应向用户说明会替换原插件后,带 overwrite=true 重发本请求。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "从注册表安装插件", "parameters": [ { "description": "安装请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.registryInstallRequest" } } ], "responses": { "200": { "description": "安装结果(更新已有插件)", "schema": { "$ref": "#/definitions/handlers.jsPluginUploadResponse" } }, "201": { "description": "安装结果(新插件)", "schema": { "$ref": "#/definitions/handlers.jsPluginUploadResponse" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "409": { "description": "entry_path 已被另一个作者的插件占用,需用户确认后带 overwrite=true 重试", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "下载或安装失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/registry/refresh": { "post": { "security": [ { "BearerAuth": [] } ], "description": "拉取订阅源(含递归 includes),去重合并后返回分页的可用插件列表。每个插件标注是否已安装及是否有更新。默认拉取单个 registry_url,可选传入 token 字段访问需要认证的私有源(如 GitHub 私有仓库 PAT)。当 all_sources=true 时忽略 registry_url/token,改为聚合已保存的所有启用订阅源(各源用自身存储的 token)。\n去重键为 entry_path + identity(identity = 规范化 author,author 为空时用 updateUrl 的 GitHub owner/repo 兜底):entry_path 相同但作者不同的插件会各自成行,同一插件被多个源收录时仍只显示一条(保留高版本)。\n若某条目的 entry_path 已被本地一个**不同作者**的插件占用,返回 installed=false、conflict=true,并在 conflict_with 中描述占用者;此时安装该插件需要用户确认覆盖。\n拉取结果在服务端缓存 5 分钟:分页与搜索都在缓存的完整列表上做切片/过滤,不会重复拉取远端。传 force=true 绕过缓存强制重拉(供「刷新」按钮使用,翻页与搜索不要传)。安装状态(installed/has_update/conflict)不受缓存影响,每次请求都从数据库实时计算。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "刷新插件注册表", "parameters": [ { "description": "刷新请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.registryRefreshRequest" } } ], "responses": { "200": { "description": "插件列表", "schema": { "$ref": "#/definitions/handlers.registryRefreshResponse" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "拉取注册表失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/storage/cleanup": { "post": { "security": [ { "BearerAuth": [] } ], "description": "删除 plugin_storage 表中不属于任何已安装插件的数据。当插件被卸载后,其持久化存储数据会保留在数据库中;此端点用于清理这些无主数据。", "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "清理孤儿持久化存储", "responses": { "200": { "description": "清理完成", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/update-all": { "post": { "security": [ { "BearerAuth": [] } ], "description": "检查并更新所有具有远程更新源的 JS 插件。跳过无 update_url 的插件和已是最新版的插件,逐个下载并安装更新,失败不中断其他插件的更新流程。设置 force=true 可跳过版本检查强制重新下载安装所有插件。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "批量更新所有 JS 插件", "parameters": [ { "description": "请求参数", "name": "body", "in": "body", "schema": { "type": "object" } } ], "responses": { "200": { "description": "批量更新结果", "schema": { "$ref": "#/definitions/handlers.jsPluginBatchUpdateResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/upload": { "post": { "security": [ { "BearerAuth": [] } ], "description": "上传新的 JS 插件文件(.jsplugin.zip 压缩包)", "consumes": [ "multipart/form-data" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "上传安装 JS 插件", "parameters": [ { "type": "file", "description": "JS插件文件 (.jsplugin.zip)", "name": "file", "in": "formData", "required": true } ], "responses": { "201": { "description": "上传成功", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "请求数据错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/{id}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "根据插件ID获取 JS 插件的详细信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "获取 JS 插件详情", "parameters": [ { "type": "integer", "description": "插件ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "JS插件信息", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "插件不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "上传新的 JS 插件文件以更新现有插件", "consumes": [ "multipart/form-data" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "更新 JS 插件", "parameters": [ { "type": "integer", "description": "插件ID", "name": "id", "in": "path", "required": true }, { "type": "file", "description": "JS插件文件 (.jsplugin.zip)", "name": "file", "in": "formData", "required": true } ], "responses": { "200": { "description": "更新成功", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "请求数据错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "插件不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } }, "delete": { "security": [ { "BearerAuth": [] } ], "description": "根据插件ID删除 JS 插件。可通过 keep_data 参数保留插件数据目录(文件系统存储),持久化存储(数据库)始终保留。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "删除 JS 插件", "parameters": [ { "type": "integer", "description": "插件ID", "name": "id", "in": "path", "required": true }, { "type": "string", "description": "是否保留插件数据目录(true/false,默认 false)", "name": "keep_data", "in": "query" } ], "responses": { "200": { "description": "删除成功", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "插件不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/{id}/check-update": { "get": { "security": [ { "BearerAuth": [] } ], "description": "检查指定 JS 插件的远程更新", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "检查 JS 插件更新", "parameters": [ { "type": "integer", "description": "插件ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "更新信息", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "插件不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/{id}/disable": { "post": { "security": [ { "BearerAuth": [] } ], "description": "禁用指定的 JS 插件", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "禁用 JS 插件", "parameters": [ { "type": "integer", "description": "插件ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "禁用成功", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "插件不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/{id}/enable": { "post": { "security": [ { "BearerAuth": [] } ], "description": "启用指定的 JS 插件", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "启用 JS 插件", "parameters": [ { "type": "integer", "description": "插件ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "启用成功", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "插件不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/jsplugins/{id}/update": { "post": { "security": [ { "BearerAuth": [] } ], "description": "从远程下载并更新指定的 JS 插件。设置 force=true 可跳过版本检查强制重新下载安装。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "下载并更新 JS 插件", "parameters": [ { "type": "integer", "description": "插件ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "更新成功", "schema": { "type": "object", "additionalProperties": true } }, "401": { "description": "未授权", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "插件不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/logs/export": { "get": { "security": [ { "BearerAuth": [] } ], "description": "将后端落盘的日志文件(\u003cdata_dir\u003e/logs/ 下按天轮转的文件)按时间从旧到新拼接,逐行脱敏后作为纯文本附件返回,触发浏览器下载。脱敏会抹除密钥/token/密码、Authorization/Cookie 头、URL 内嵌凭证、客户端 IP 主机位、用户主目录名等敏感信息,便于用户安全地附到 issue。远程服务器、桌面 Bundle、移动 Bundle 三种模式下均可用(均由同一份后端提供该端点)。无日志文件时返回仅含提示行的文本。", "produces": [ "text/plain" ], "tags": [ "设置" ], "summary": "导出后端日志", "responses": { "200": { "description": "脱敏后的后端日志(text/plain)", "schema": { "type": "file" } }, "500": { "description": "读取日志目录失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/play-history": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回指定播放上下文内最近播放过的歌曲,按最后播放时间倒序,含完整歌曲详情。\n「播放上下文」由 context_type + context_key 二元组标识:歌单为 (playlist, 歌单 ID),分面维度为 (artist, 歌手名) / (album, 专辑名) 等。\n同一上下文内按歌曲去重(重复播放只刷新时间并累加 play_count),最多保留最近 50 条,因此本端点不分页。\n记录由 POST /songs/{id}/played 在 type=play 时写入。歌曲从库中删除时其历史自动级联清理;歌曲仅被移出歌单时历史仍保留,客户端起播时自行判定失效。", "produces": [ "application/json" ], "tags": [ "播放历史" ], "summary": "查询播放上下文的播放历史", "parameters": [ { "enum": [ "playlist", "artist", "album", "genre", "year", "decade", "language", "style" ], "type": "string", "description": "播放上下文类型", "name": "context_type", "in": "query", "required": true }, { "type": "string", "description": "播放上下文标识:playlist 传歌单 ID,分面维度传该维度取值", "name": "context_key", "in": "query", "required": true }, { "type": "integer", "description": "返回条数,缺省 50,上限 50", "name": "limit", "in": "query" } ], "responses": { "200": { "description": "成功返回播放历史列表", "schema": { "$ref": "#/definitions/models.PlayHistoryListResponse" } }, "400": { "description": "context_type 不支持或缺少 context_key", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } }, "delete": { "security": [ { "BearerAuth": [] } ], "description": "删除指定播放上下文内的全部播放记录,返回实际删除条数。上下文不存在或本就没有记录时返回 deleted=0,不视为错误。", "produces": [ "application/json" ], "tags": [ "播放历史" ], "summary": "清空播放上下文的播放历史", "parameters": [ { "enum": [ "playlist", "artist", "album", "genre", "year", "decade", "language", "style" ], "type": "string", "description": "播放上下文类型", "name": "context_type", "in": "query", "required": true }, { "type": "string", "description": "播放上下文标识:playlist 传歌单 ID,分面维度传该维度取值", "name": "context_key", "in": "query", "required": true } ], "responses": { "200": { "description": "成功返回 {deleted: 删除条数}", "schema": { "type": "object", "additionalProperties": { "type": "integer" } } }, "400": { "description": "context_type 不支持或缺少 context_key", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/play-history/entry": { "delete": { "security": [ { "BearerAuth": [] } ], "description": "从指定播放上下文中删除某首歌的播放记录。典型用途:清理已被移出歌单、在历史面板里显示为失效的条目。", "produces": [ "application/json" ], "tags": [ "播放历史" ], "summary": "删除单条播放历史", "parameters": [ { "enum": [ "playlist", "artist", "album", "genre", "year", "decade", "language", "style" ], "type": "string", "description": "播放上下文类型", "name": "context_type", "in": "query", "required": true }, { "type": "string", "description": "播放上下文标识:playlist 传歌单 ID,分面维度传该维度取值", "name": "context_key", "in": "query", "required": true }, { "type": "integer", "description": "要删除的歌曲 ID", "name": "song_id", "in": "query", "required": true } ], "responses": { "204": { "description": "删除成功,无内容" }, "400": { "description": "context_type 不支持、缺少 context_key 或无效的 song_id", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "该上下文中不存在此歌曲的播放记录", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "服务器错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/playlists": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取歌单列表,支持按类型过滤、关键词搜索和分页。默认排除隐藏歌单,传 exclude_labels=none 显示全部", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "获取歌单列表", "parameters": [ { "enum": [ "normal", "radio" ], "type": "string", "description": "歌单类型", "name": "type", "in": "query" }, { "type": "string", "description": "搜索关键词(模糊匹配歌单名称/描述)", "name": "keyword", "in": "query" }, { "type": "string", "default": "hidden", "description": "要排除的标签(逗号分隔), 默认排除 hidden; 传 none 显示全部", "name": "exclude_labels", "in": "query" }, { "type": "integer", "default": 20, "description": "每页数量", "name": "limit", "in": "query" }, { "type": "integer", "default": 0, "description": "偏移量", "name": "offset", "in": "query" } ], "responses": { "200": { "description": "成功返回歌单列表", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "post": { "security": [ { "BearerAuth": [] } ], "description": "创建一个新的歌单", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "创建歌单", "parameters": [ { "description": "歌单信息", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.Playlist" } } ], "responses": { "201": { "description": "创建成功", "schema": { "$ref": "#/definitions/models.Playlist" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "创建失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/batch-delete": { "post": { "security": [ { "BearerAuth": [] } ], "description": "根据歌单 ID 列表批量删除歌单,内置歌单会被跳过。请求体 delete_songs=true 时,同时删除仅属于这些歌单的孤儿歌曲(不属于任何其他歌单)——本地歌曲连同磁盘文件一并删除。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "批量删除歌单", "parameters": [ { "description": "批量删除请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.BatchDeletePlaylistsRequest" } } ], "responses": { "200": { "description": "删除成功", "schema": { "$ref": "#/definitions/models.BatchDeletePlaylistsResponse" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "删除失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/export": { "get": { "security": [ { "BearerAuth": [] } ], "description": "导出所有歌单(含曲目关联)为 JSON 文件,响应头携带 Content-Disposition 直接触发浏览器下载", "produces": [ "application/json" ], "tags": [ "数据备份" ], "summary": "导出歌单备份", "responses": { "200": { "description": "歌单备份 JSON 文件", "schema": { "type": "file" } }, "500": { "description": "导出失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/import": { "post": { "security": [ { "BearerAuth": [] } ], "description": "从 multipart/form-data 上传的 JSON 备份文件还原歌单。已存在的歌单按名称合并,曲目按内容匹配去重。", "consumes": [ "multipart/form-data" ], "produces": [ "application/json" ], "tags": [ "数据备份" ], "summary": "导入歌单备份", "parameters": [ { "type": "file", "description": "歌单备份 JSON 文件", "name": "file", "in": "formData", "required": true } ], "responses": { "200": { "description": "导入结果(新建/合并歌单数、新建/匹配曲目数)", "schema": { "$ref": "#/definitions/models.ImportResult" } }, "400": { "description": "请求格式错误或文件无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "导入失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/reorder": { "put": { "security": [ { "BearerAuth": [] } ], "description": "重新排序歌单列表", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "重新排序歌单列表", "parameters": [ { "description": "歌单 ID 列表", "name": "request", "in": "body", "required": true, "schema": { "type": "object", "properties": { "playlist_ids": { "type": "array", "items": { "type": "integer", "format": "int64" } } } } } ], "responses": { "200": { "description": "排序成功", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "排序失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "根据歌单ID获取详细信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "获取单个歌单详情", "parameters": [ { "type": "integer", "description": "歌单ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "成功返回歌单详情", "schema": { "$ref": "#/definitions/models.Playlist" } }, "400": { "description": "无效的歌单ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌单不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "更新歌单信息。支持通过 cover_song_id 从指定歌曲复制封面,与 cover_path/cover_url 互斥且优先级更高", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "更新歌单", "parameters": [ { "type": "integer", "description": "歌单ID", "name": "id", "in": "path", "required": true }, { "description": "歌单信息", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.UpdatePlaylistRequest" } } ], "responses": { "200": { "description": "更新成功", "schema": { "$ref": "#/definitions/models.Playlist" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "更新失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "delete": { "security": [ { "BearerAuth": [] } ], "description": "根据歌单ID删除歌单。delete_songs=true 时,同时删除仅属于本歌单的孤儿歌曲(不属于任何其他歌单,含内置的收藏/电台收藏保护)——本地歌曲连同磁盘文件一并删除,网络/电台歌曲清理数据库记录与缓存。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "删除歌单", "parameters": [ { "type": "integer", "description": "歌单ID", "name": "id", "in": "path", "required": true }, { "type": "boolean", "description": "是否一并删除仅属于本歌单的孤儿歌曲(含本地文件),默认 false", "name": "delete_songs", "in": "query" } ], "responses": { "200": { "description": "删除成功,含连带清理的歌曲数 deleted_songs", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "无效的歌单ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "删除失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}/cover": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回歌单封面图片文件。可选 query 参数 w:把本地封面等比缩放到该宽度(物理像素,绝不放大、上限 1024)后以 JPEG 返回,用于 Web 端降低 GPU 纹理体积(songloft-org/songloft#309);缺省或非法时返回原图。缩略仅作用于本地封面,远程代理封面忽略 w。", "produces": [ "image/jpeg" ], "tags": [ "歌单管理" ], "summary": "获取歌单封面", "parameters": [ { "type": "integer", "description": "歌单ID", "name": "id", "in": "path", "required": true }, { "type": "integer", "description": "本地封面缩略目标宽度(物理像素,绝不放大,上限 1024)", "name": "w", "in": "query" } ], "responses": { "200": { "description": "封面图片", "schema": { "type": "file" } }, "404": { "description": "封面不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "读取失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "post": { "security": [ { "BearerAuth": [] } ], "description": "上传本地图片作为歌单封面", "consumes": [ "multipart/form-data" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "上传歌单封面", "parameters": [ { "type": "integer", "description": "歌单ID", "name": "id", "in": "path", "required": true }, { "type": "file", "description": "封面图片文件", "name": "file", "in": "formData", "required": true } ], "responses": { "200": { "description": "上传成功", "schema": { "$ref": "#/definitions/models.Playlist" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "上传失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}/song-ids": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回歌单内全部歌曲的 ID,顺序与 GET /playlists/{id}/songs 的默认顺序(position 升序)严格一致,不分页。\n用途:客户端需要知道「某首歌在歌单里排第几」时(如从播放历史里的某首歌接着往下播),用本端点拿到有序 ID 数组后取下标,即可直接作为 /playlists/{id}/songs 的 offset 使用,避免为此拉取全部歌曲对象。\n形态与 GET /songs/ids 对齐。", "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "获取歌单歌曲 ID 列表", "parameters": [ { "type": "integer", "description": "歌单 ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "成功返回 {ids:[1,2,3], total:3}", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "无效的歌单 ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}/songs": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取指定歌单中的歌曲,支持分页、排序和搜索", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "获取歌单中的歌曲", "parameters": [ { "type": "integer", "description": "歌单 ID", "name": "id", "in": "path", "required": true }, { "type": "integer", "default": 20, "description": "每页数量", "name": "limit", "in": "query" }, { "type": "integer", "default": 0, "description": "偏移量", "name": "offset", "in": "query" }, { "type": "string", "description": "排序字段: position(默认)/added_at/title/artist/album/duration/updated_at/file_modified_at", "name": "sort", "in": "query" }, { "type": "string", "description": "排序方向: asc(默认)/desc", "name": "order", "in": "query" }, { "type": "string", "description": "搜索关键词(匹配标题/艺术家/专辑)", "name": "keyword", "in": "query" } ], "responses": { "200": { "description": "成功返回歌曲列表", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "无效的歌单 ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "获取失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "post": { "security": [ { "BearerAuth": [] } ], "description": "将多首歌曲添加到指定歌单,跳过已存在的歌曲", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "批量添加歌曲到歌单", "parameters": [ { "type": "integer", "description": "歌单 ID", "name": "id", "in": "path", "required": true }, { "description": "歌曲 ID 列表", "name": "request", "in": "body", "required": true, "schema": { "type": "object", "properties": { "song_ids": { "type": "array", "items": { "type": "integer", "format": "int64" } } } } } ], "responses": { "200": { "description": "添加成功", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "添加失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}/songs/reorder": { "put": { "security": [ { "BearerAuth": [] } ], "description": "重新排序歌单中的歌曲", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "重新排序歌单中的歌曲", "parameters": [ { "type": "integer", "description": "歌单 ID", "name": "id", "in": "path", "required": true }, { "description": "歌曲 ID 列表", "name": "request", "in": "body", "required": true, "schema": { "type": "object", "properties": { "song_ids": { "type": "array", "items": { "type": "integer", "format": "int64" } } } } } ], "responses": { "200": { "description": "排序成功", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "排序失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}/songs/{songId}": { "delete": { "security": [ { "BearerAuth": [] } ], "description": "从指定歌单移除歌曲", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "从歌单移除歌曲", "parameters": [ { "type": "integer", "description": "歌单 ID", "name": "id", "in": "path", "required": true }, { "type": "integer", "description": "歌曲 ID", "name": "songId", "in": "path", "required": true } ], "responses": { "200": { "description": "移除成功", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "移除失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}/touch": { "post": { "security": [ { "BearerAuth": [] } ], "description": "仅更新歌单的 updated_at 字段,作为歌单级的粗粒度「最后播放时间」,也会被改名/换封面等更新操作刷新。\n歌曲级的精确播放历史见 GET /play-history?context_type=playlist\u0026context_key={id}。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "更新歌单最后播放时间", "parameters": [ { "type": "integer", "description": "歌单ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "更新成功", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "400": { "description": "无效的歌单ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "更新失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/playlists/{id}/visibility": { "put": { "security": [ { "BearerAuth": [] } ], "description": "切换歌单的隐藏状态。内置歌单(收藏、电台收藏)不允许隐藏", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌单管理" ], "summary": "设置歌单可见性", "parameters": [ { "type": "integer", "description": "歌单ID", "name": "id", "in": "path", "required": true }, { "description": "可见性设置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.SetPlaylistVisibilityRequest" } } ], "responses": { "200": { "description": "更新后的歌单", "schema": { "$ref": "#/definitions/models.Playlist" } }, "400": { "description": "请求错误或内置歌单不可隐藏", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌单不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/plugins/health": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回各音乐源插件的下载成功率、健康度分类(green/yellow/red)与最近 5 条失败原因。", "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "音源健康度", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/handlers.pluginHealthResponse" } } } } }, "/proxy": { "get": { "security": [ { "BearerAuth": [] } ], "description": "代理外部资源(图片、音频、视频流等),解决浏览器 CORS 限制。支持流式转发、Range 请求透传、Content-Type 透传和域名白名单校验", "produces": [ "application/octet-stream" ], "tags": [ "资源代理" ], "summary": "代理外部资源", "parameters": [ { "type": "string", "description": "目标资源的 URL(URL 编码)", "name": "url", "in": "query", "required": true } ], "responses": { "200": { "description": "代理的资源内容", "schema": { "type": "file" } }, "206": { "description": "部分内容(Range 请求)", "schema": { "type": "file" } }, "400": { "description": "缺少 url 参数或 URL 无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "403": { "description": "域名不在白名单中", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "502": { "description": "上游请求失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/scan": { "post": { "security": [ { "BearerAuth": [] } ], "description": "异步扫描音乐目录并导入新发现的音乐文件到数据库,立即返回,可通过进度接口查询状态。\nreimport=true 时对已入库文件也重新提取元数据;默认 false 走增量(跳过已存在且时长有效的文件)。\npaths 为目录级定向扫描(Issue #262):省略/为空时扫描整个音乐根目录;非空时只扫描给定目录(含子目录),\n且过期记录清理仅收敛到这些目录之内(不影响其余曲库)。每个目录必须位于音乐根目录之下,否则返回 400。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "扫描并导入本地音乐", "parameters": [ { "description": "扫描请求参数", "name": "request", "in": "body", "schema": { "$ref": "#/definitions/handlers.ScanRequest" } } ], "responses": { "200": { "description": "扫描任务已启动", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "指定目录不在音乐目录下", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "409": { "description": "扫描正在进行中", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "启动扫描失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/scan/cancel": { "post": { "security": [ { "BearerAuth": [] } ], "description": "取消正在进行的扫描任务", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "取消扫描", "responses": { "200": { "description": "取消成功", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "没有正在进行的扫描任务", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/scan/dir-names": { "get": { "security": [ { "BearerAuth": [] } ], "description": "递归收集音乐目录下所有唯一的目录名称,按字母排序返回,用于排除目录名称的自动补全", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取所有目录名称", "responses": { "200": { "description": "目录名称列表", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "收集目录名称失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/scan/directories": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回指定路径下的一级子目录列表,用于目录树懒加载。path 为空时返回音乐根目录下的子目录", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取子目录列表", "parameters": [ { "type": "string", "description": "目录路径(为空时使用音乐根目录)", "name": "path", "in": "query" } ], "responses": { "200": { "description": "子目录列表", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "无效的路径", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "读取目录失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/scan/fingerprints": { "post": { "security": [ { "BearerAuth": [] } ], "description": "异步为本地歌曲计算音频指纹,需要 ffmpeg 支持 chromaprint。若已有任务在运行则打断重启。传入 recompute_all=true 时清空已有指纹后重新计算全部;传入 retry_failed=true 时仅重置失败项的「已尝试」标记后重试(已算好的指纹保留,适用于 ffmpeg 能力升级后恢复失败歌曲)。两者同时传入时 recompute_all 优先。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "触发批量指纹计算", "parameters": [ { "description": "计算选项", "name": "request", "in": "body", "schema": { "$ref": "#/definitions/handlers.startFingerprintRequest" } } ], "responses": { "200": { "description": "任务已启动", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "chromaprint 不可用", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/scan/fingerprints/cancel": { "post": { "security": [ { "BearerAuth": [] } ], "description": "停止正在运行的批量指纹计算任务并杀掉其 ffmpeg 子进程。指纹任务不挂在扫描的取消通道上(扫描「完成」后该通道已关闭),所以需要独立的取消入口。任务不在运行时返回 cancelled=false。", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "中断指纹计算", "responses": { "200": { "description": "返回 cancelled 字段", "schema": { "type": "object", "additionalProperties": { "type": "boolean" } } } } } }, "/scan/fingerprints/progress": { "get": { "security": [ { "BearerAuth": [] } ], "description": "查询当前指纹计算任务的进度", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取指纹计算进度", "responses": { "200": { "description": "计算进度", "schema": { "$ref": "#/definitions/services.FingerprintProgress" } } } } }, "/scan/fingerprints/status": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回 ffmpeg chromaprint 可用性、本地歌曲指纹计算统计(含尝试失败数)以及「扫描后自动计算指纹」开关状态", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取指纹计算状态", "responses": { "200": { "description": "指纹状态", "schema": { "$ref": "#/definitions/handlers.FingerprintStatus" } }, "500": { "description": "查询指纹统计失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/scan/progress": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取当前扫描任务的进度信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取扫描进度", "responses": { "200": { "description": "扫描进度信息", "schema": { "$ref": "#/definitions/services.ScanProgress" } } } } }, "/settings/auto-scan": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回自动扫描的启用状态和扫描间隔(秒)。默认关闭,间隔 3600 秒(1 小时)。", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取自动扫描配置", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/handlers.AutoScanSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "设置自动扫描的启用状态和扫描间隔。interval_seconds 有效范围 [60, 86400]。更新后立即生效(无需重启)。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "更新自动扫描配置", "parameters": [ { "description": "自动扫描配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.AutoScanSetting" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/handlers.AutoScanSetting" } }, "400": { "description": "请求格式错误或参数无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/equalizer": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取全局均衡器(EQ)配置,包含启用状态、预设名称和 10 段频段增益(31Hz–16kHz,单位 dB,范围 -12 ~ +12)。未配置时返回默认值(关闭 + flat 预设 + 全 0)。", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取均衡器配置", "responses": { "200": { "description": "均衡器配置", "schema": { "$ref": "#/definitions/handlers.equalizerSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "保存全局均衡器(EQ)配置。bands 必须包含 10 个元素,每个值在 -12 ~ +12 范围内(单位 dB)。preset 为预设名称(flat/rock/pop/jazz/classical/bass_boost/treble_boost/vocal/custom)。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "保存均衡器配置", "parameters": [ { "description": "均衡器配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.equalizerSetting" } } ], "responses": { "200": { "description": "保存后的均衡器配置", "schema": { "$ref": "#/definitions/handlers.equalizerSetting" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/github-proxy": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取检查更新 / 升级时使用的 GitHub 代理前缀(如 https://ghfast.top/)。前端会记住上次使用的代理并在检查更新时自动带上。未配置时返回空字符串(直连)。", "produces": [ "application/json" ], "tags": [ "系统升级" ], "summary": "获取 GitHub 更新代理配置", "responses": { "200": { "description": "GitHub 更新代理配置", "schema": { "$ref": "#/definitions/handlers.githubProxySetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "设置检查更新 / 升级使用的 GitHub 代理前缀(如 https://ghfast.top/)。设为空字符串则直连。仅持久化,不影响其它模块的全局 HTTP 代理。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统升级" ], "summary": "保存 GitHub 更新代理配置", "parameters": [ { "description": "GitHub 更新代理配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.githubProxySetting" } } ], "responses": { "200": { "description": "保存后的配置", "schema": { "$ref": "#/definitions/handlers.githubProxySetting" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/hls-proxy": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取“HLS 电台流通过本机反代回客户端”开关的当前状态", "produces": [ "application/json" ], "tags": [ "电台与 HLS" ], "summary": "获取 HLS 代理开关", "responses": { "200": { "description": "返回 enabled 字段表示开关状态", "schema": { "type": "object", "additionalProperties": { "type": "boolean" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "开启/关闭 HLS 反代。开启时电台切片字节全部经本机转发(解决源站 Referer/CORS 拦截),关闭时仅 302 给 player。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "电台与 HLS" ], "summary": "更新 HLS 代理开关", "parameters": [ { "description": "开关请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.hlsProxySettingRequest" } } ], "responses": { "200": { "description": "返回 enabled 字段表示更新后的开关状态", "schema": { "type": "object", "additionalProperties": { "type": "boolean" } } }, "400": { "description": "请求格式错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/http-proxy": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取全局 HTTP 代理地址。所有后端外发请求(插件下载、注册表拉取、升级检查等)会通过此代理转发。未配置时返回空字符串(直连)。", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取 HTTP 代理配置", "responses": { "200": { "description": "代理配置", "schema": { "$ref": "#/definitions/handlers.httpProxySetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "设置全局 HTTP 代理地址(如 http://192.168.1.1:7890)。设为空字符串则关闭代理。保存后即时生效,无需重启。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "保存 HTTP 代理配置", "parameters": [ { "description": "代理配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.httpProxySetting" } } ], "responses": { "200": { "description": "保存后的代理配置", "schema": { "$ref": "#/definitions/handlers.httpProxySetting" } }, "400": { "description": "请求格式错误或代理地址无效", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/library-browse": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取用户自定义的曲库统一浏览页视图显示与顺序。共 14 个视图,分三组:歌曲组 all(全部)/local(本地)/remote(网络)/radio(电台);分类组 artist(歌手)/album(专辑)/genre(流派)/year(年份)/decade(年代)/language(语种)/style(风格);歌单组 playlist(全部歌单)/playlist_normal(普通歌单)/playlist_radio(电台歌单)。未配置时返回默认(全部可见、默认顺序)。返回始终包含完整 14 项。", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取曲库浏览视图配置", "responses": { "200": { "description": "曲库浏览视图配置", "schema": { "$ref": "#/definitions/handlers.libraryBrowseSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "保存用户自定义的曲库浏览页视图显示与顺序。每个 view 的 key 必须属于合法的 14 个 key 且不能重复;未出现的 key 会按默认顺序补到末尾(visible=true),保证返回完整 14 项。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "保存曲库浏览视图配置", "parameters": [ { "description": "曲库浏览视图配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.libraryBrowseSetting" } } ], "responses": { "200": { "description": "保存后的配置", "schema": { "$ref": "#/definitions/handlers.libraryBrowseSetting" } }, "400": { "description": "请求格式错误或校验失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/log-level": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回当前 slog 全局日志等级", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取日志等级", "responses": { "200": { "description": "返回 level 字段:debug/info/warn/error", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "切换 slog 全局日志等级并持久化。新等级即时生效,重启后从 DB 恢复。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "更新日志等级", "parameters": [ { "description": "等级请求(debug/info/warn/error)", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.logLevelSettingRequest" } } ], "responses": { "200": { "description": "返回更新后的 level", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "400": { "description": "请求格式错误或等级非法", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/music-path": { "get": { "security": [ { "BearerAuth": [] } ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取音乐路径与扫描排除配置", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/handlers.MusicPathSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "写入 music_path 配置并触发 Scanner 重建 + 清理排除目录中的歌曲(与 admin /configs PUT 的副作用一致)。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "更新音乐路径与扫描排除配置", "parameters": [ { "description": "配置内容", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.MusicPathSetting" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/handlers.MusicPathSetting" } }, "400": { "description": "请求格式错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/plugin-auto-update": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取“后台自动更新已安装插件”开关的当前状态。开启后,服务会在启动后延迟数分钟检查一次、之后每 6 小时定时检查所有具有远程更新源的插件并自动更新。默认关闭。", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取插件自动更新开关", "responses": { "200": { "description": "返回 enabled 字段表示开关状态", "schema": { "$ref": "#/definitions/handlers.pluginAutoUpdateSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "开启/关闭插件后台自动更新。开启后后台 ticker 会定时对有更新源的插件执行“检查更新 + 下载安装 + 热重载”。开关即时生效,无需重启。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "保存插件自动更新开关", "parameters": [ { "description": "开关请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.pluginAutoUpdateSetting" } } ], "responses": { "200": { "description": "返回 enabled 字段表示更新后的开关状态", "schema": { "$ref": "#/definitions/handlers.pluginAutoUpdateSetting" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/plugin-keep-alive": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取不会被自动休眠的插件 entryPath 列表。白名单中的插件即使空闲超过 10 分钟也不会被卸载。未配置时返回空列表。", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取插件常驻白名单", "responses": { "200": { "description": "常驻白名单", "schema": { "$ref": "#/definitions/handlers.pluginKeepAliveSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "设置不会被自动休眠的插件 entryPath 列表。保存后即时生效,白名单中的插件将跳过空闲检查,始终保持运行。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "保存插件常驻白名单", "parameters": [ { "description": "常驻白名单", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.pluginKeepAliveSetting" } } ], "responses": { "200": { "description": "保存后的白名单", "schema": { "$ref": "#/definitions/handlers.pluginKeepAliveSetting" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/plugin-registries": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取用户保存的所有插件注册表订阅源 URL。未配置时返回空列表。", "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "获取插件订阅源列表", "responses": { "200": { "description": "订阅源列表", "schema": { "$ref": "#/definitions/handlers.pluginRegistriesSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "保存用户配置的插件注册表订阅源 URL 列表。每个源包含 URL、名称和是否启用。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "JS插件管理" ], "summary": "保存插件订阅源列表", "parameters": [ { "description": "订阅源列表", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.pluginRegistriesSetting" } } ], "responses": { "200": { "description": "保存后的订阅源列表", "schema": { "$ref": "#/definitions/handlers.pluginRegistriesSetting" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/proxy-private-allowlist": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取「允许 /proxy 代理的私网地址」白名单。默认空数组:私网 / 回环 / 链路本地地址一律拒绝(SSRF 防护)。每条为单个 IP(如 192.168.1.100)或 CIDR 网段(如 192.168.1.0/24)。仅影响通用 /proxy 端点,不影响 HLS 反代。", "produces": [ "application/json" ], "tags": [ "资源代理" ], "summary": "获取私网代理白名单", "responses": { "200": { "description": "返回 allowlist 字段,当前白名单条目列表", "schema": { "$ref": "#/definitions/handlers.proxyAllowlistSettingRequest" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "覆盖式更新私网代理白名单。每条须为单个 IP(IPv4/IPv6)或 CIDR 网段;空白条目自动忽略。任一条目非法返回 400。设置后,目标解析到的私网地址若被白名单覆盖,/proxy 即放行(用于「公网 Songloft 代理内网 WebDAV」等场景)。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "资源代理" ], "summary": "更新私网代理白名单", "parameters": [ { "description": "白名单条目列表(IP 或 CIDR)", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.proxyAllowlistSettingRequest" } } ], "responses": { "200": { "description": "返回更新后的白名单", "schema": { "$ref": "#/definitions/handlers.proxyAllowlistSettingRequest" } }, "400": { "description": "请求格式错误或含非法条目", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/remote-title-source": { "get": { "security": [ { "BearerAuth": [] } ], "description": "tag:元数据刷新时用音频标签覆盖标题;filename(默认):保持文件名作为标题,不覆盖。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取网络歌曲标题来源配置", "responses": { "200": { "description": "返回 title_source 字段", "schema": { "$ref": "#/definitions/handlers.remoteTitleSourceRequest" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "tag:元数据刷新时用音频标签覆盖标题;filename(默认):保持文件名作为标题,不覆盖。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "更新网络歌曲标题来源配置", "parameters": [ { "description": "标题来源配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.remoteTitleSourceRequest" } } ], "responses": { "200": { "description": "返回 title_source 字段", "schema": { "$ref": "#/definitions/handlers.remoteTitleSourceRequest" } }, "400": { "description": "请求格式错误或参数无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/scan-auto-create-playlists": { "get": { "security": [ { "BearerAuth": [] } ], "description": "控制扫描完成后是否根据音乐目录结构自动创建歌单。默认启用(true)。关闭后扫描仅入库歌曲,不再自动建歌单。", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取「扫描后自动创建歌单」开关", "responses": { "200": { "description": "返回 enabled 字段", "schema": { "type": "object", "additionalProperties": { "type": "boolean" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "控制扫描完成后是否根据音乐目录结构自动创建歌单。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "更新「扫描后自动创建歌单」开关", "parameters": [ { "description": "开关请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.scanAutoCreatePlaylistsRequest" } } ], "responses": { "200": { "description": "返回 enabled 字段", "schema": { "type": "object", "additionalProperties": { "type": "boolean" } } }, "400": { "description": "请求格式错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/scan-auto-fingerprint": { "get": { "security": [ { "BearerAuth": [] } ], "description": "控制扫描完成后是否自动为缺失指纹的本地歌曲计算 chromaprint 音频指纹。默认关闭(false):指纹只服务于「重复歌曲检测」和插件歌词/封面搜索,属按需功能,全库自动计算会长时间占用 CPU。关闭时可在重复检测页手动触发 POST /scan/fingerprints。", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取「扫描后自动计算音频指纹」开关", "responses": { "200": { "description": "返回 enabled 字段", "schema": { "type": "object", "additionalProperties": { "type": "boolean" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "开启后每次扫描结束会在后台为缺失指纹的本地歌曲计算 chromaprint 指纹(并发按 CPU 自适应,单文件采样前 120 秒,失败只尝试一次)。大音乐库开启前请留意 CPU 开销。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "更新「扫描后自动计算音频指纹」开关", "parameters": [ { "description": "开关请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.scanAutoFingerprintRequest" } } ], "responses": { "200": { "description": "返回 enabled 字段", "schema": { "type": "object", "additionalProperties": { "type": "boolean" } } }, "400": { "description": "请求格式错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/scan-playlist-mode": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回扫描后自动创建歌单的目录归并模式。directory:每个文件夹生成独立歌单;top_level:按一级子目录合并歌单;bubble_up:歌曲同时出现在所有上级文件夹歌单。默认 directory。", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取歌单创建方式", "responses": { "200": { "description": "返回 mode 字段", "schema": { "$ref": "#/definitions/handlers.scanPlaylistModeResponse" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "设置扫描后自动创建歌单的目录归并模式。directory:每个文件夹生成独立歌单;top_level:按一级子目录合并歌单;bubble_up:歌曲同时出现在所有上级文件夹歌单。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "更新歌单创建方式", "parameters": [ { "description": "模式请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.scanPlaylistModeRequest" } } ], "responses": { "200": { "description": "返回 mode 字段", "schema": { "$ref": "#/definitions/handlers.scanPlaylistModeResponse" } }, "400": { "description": "请求格式错误或 mode 值非法", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/scan-title-source": { "get": { "security": [ { "BearerAuth": [] } ], "description": "tag:优先使用音频标签中的标题(默认);filename:始终使用文件名(不含扩展名)作为标题。切换后需以「重新导入」模式扫描才能生效。", "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "获取扫描标题来源配置", "responses": { "200": { "description": "返回 title_source 字段", "schema": { "$ref": "#/definitions/handlers.scanTitleSourceRequest" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "tag:优先使用音频标签中的标题;filename:始终使用文件名(不含扩展名)作为标题。切换后需以「重新导入」模式扫描才能生效。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "扫描管理" ], "summary": "更新扫描标题来源配置", "parameters": [ { "description": "标题来源配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.scanTitleSourceRequest" } } ], "responses": { "200": { "description": "返回 title_source 字段", "schema": { "$ref": "#/definitions/handlers.scanTitleSourceRequest" } }, "400": { "description": "请求格式错误或参数无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "保存配置失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/settings/tab-config": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取用户自定义的底部导航栏 Tab 配置。首页和设置固定显示,歌曲库和歌单可关闭,可选项(歌曲库+歌单+插件 Tab)总数不超过 10 个。未配置时返回默认值(4 个 Tab:首页、歌曲库、歌单、设置)。移动端超过 5 个时自动折叠到「更多」菜单,桌面端侧边栏可全部展示。", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取底部导航栏 Tab 配置", "responses": { "200": { "description": "Tab 配置", "schema": { "$ref": "#/definitions/handlers.tabConfigSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "保存用户自定义的底部导航栏 Tab 配置。首页和设置固定显示(不在配置中),可选项为歌曲库、歌单和插件 Tab,可选项总数不超过 10 个。每个插件 Tab 的 entry_path 和 name 不能为空,且不能重复。移动端超过 5 个时自动折叠到「更多」菜单。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "保存底部导航栏 Tab 配置", "parameters": [ { "description": "Tab 配置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.tabConfigSetting" } } ], "responses": { "200": { "description": "保存后的 Tab 配置", "schema": { "$ref": "#/definitions/handlers.tabConfigSetting" } }, "400": { "description": "请求格式错误或校验失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/settings/user-preferences": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取用户跨设备同步的偏好设置,包括主题、播放模式、视图模式、音质、缓存上限和音量。未配置时返回默认值。", "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "获取用户偏好设置", "responses": { "200": { "description": "用户偏好设置", "schema": { "$ref": "#/definitions/handlers.userPreferencesSetting" } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "保存用户跨设备同步的偏好设置。客户端登录后拉取、修改偏好时推送,实现多设备间偏好同步。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "设置" ], "summary": "保存用户偏好设置", "parameters": [ { "description": "用户偏好设置", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handlers.userPreferencesSetting" } } ], "responses": { "200": { "description": "保存后的用户偏好设置", "schema": { "$ref": "#/definitions/handlers.userPreferencesSetting" } }, "400": { "description": "请求格式错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "保存配置失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/songs": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取歌曲列表,支持按类型过滤、关键词搜索和分页。默认排除隐藏歌单里的歌,传 exclude_playlist_labels=none 显示全部", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取歌曲列表", "parameters": [ { "enum": [ "local", "remote", "radio" ], "type": "string", "description": "歌曲类型", "name": "type", "in": "query" }, { "type": "string", "description": "搜索关键词", "name": "keyword", "in": "query" }, { "type": "string", "description": "按 file_path 前缀过滤(如 music/Pop)", "name": "path_prefix", "in": "query" }, { "type": "string", "description": "按流派精确过滤", "name": "genre", "in": "query" }, { "type": "string", "description": "按歌手精确过滤", "name": "artist", "in": "query" }, { "type": "string", "description": "按专辑精确过滤", "name": "album", "in": "query" }, { "type": "string", "description": "按语种精确过滤", "name": "language", "in": "query" }, { "type": "string", "description": "按风格精确过滤", "name": "style", "in": "query" }, { "type": "integer", "description": "按发行年份精确过滤", "name": "year", "in": "query" }, { "type": "integer", "description": "按年代过滤(起始年,如 1990 匹配 1990-1999)", "name": "decade", "in": "query" }, { "type": "string", "default": "hidden", "description": "排除属于这些 label 歌单的歌曲(逗号分隔), 默认 hidden; 传 none 显示全部", "name": "exclude_playlist_labels", "in": "query" }, { "type": "integer", "default": 20, "description": "每页数量", "name": "limit", "in": "query" }, { "type": "integer", "default": 0, "description": "偏移量", "name": "offset", "in": "query" }, { "enum": [ "id", "title", "artist", "album", "duration", "added_at", "updated_at", "file_modified_at", "year", "genre" ], "type": "string", "description": "排序字段,缺省 added_at", "name": "sort", "in": "query" }, { "enum": [ "asc", "desc" ], "type": "string", "description": "排序方向,缺省 desc", "name": "order", "in": "query" } ], "responses": { "200": { "description": "成功返回歌曲列表", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/batch-delete": { "post": { "security": [ { "BearerAuth": [] } ], "description": "根据歌曲 ID 列表批量删除歌曲。设置 delete_files=true 时同步删除本地音频文件(用于去重等场景)", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "批量删除歌曲", "parameters": [ { "description": "批量删除请求", "name": "request", "in": "body", "required": true, "schema": { "$ref": "#/definitions/models.BatchDeleteSongsRequest" } } ], "responses": { "200": { "description": "删除成功", "schema": { "$ref": "#/definitions/models.BatchDeleteSongsResponse" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "删除失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/clean": { "post": { "security": [ { "BearerAuth": [] } ], "description": "清理本地歌曲中文件已不存在或位于排除目录中的记录,同时删除关联的封面文件", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "清理无效的本地歌曲", "responses": { "200": { "description": "清理成功", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "清理失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/duplicates": { "get": { "security": [ { "BearerAuth": [] } ], "description": "通过音频指纹查询本地歌曲中内容相同的重复组", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取重复歌曲组", "responses": { "200": { "description": "重复歌曲组列表", "schema": { "type": "object", "additionalProperties": true } } } } }, "/songs/facets": { "get": { "security": [ { "BearerAuth": [] } ], "description": "按指定维度聚合曲库,返回该维度下非空取值、各自的歌曲数量及一首代表歌曲的封面 URL,用于「分类浏览」的卡片网格。\n支持维度:genre(流派)/artist(歌手)/album(专辑)/language(语种)/style(风格)/year(年份)/decade(年代)。\nyear/decade 的 value 为数字字符串(年代如 \"1990\" 表示 1990-1999)。取到某取值后可用 /songs?\u003cfield\u003e=\u003cvalue\u003e 拉取该分类下歌曲。\n支持 keyword 模糊搜索取值、limit/offset 分页、sort(count|name)/order 排序;返回 total 为该维度去重取值总数。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "曲库标签分类聚合", "parameters": [ { "enum": [ "genre", "artist", "album", "language", "style", "year", "decade" ], "type": "string", "description": "聚合维度", "name": "field", "in": "query", "required": true }, { "type": "string", "description": "对取值模糊搜索", "name": "keyword", "in": "query" }, { "type": "integer", "description": "分页大小,缺省 20,上限 100000", "name": "limit", "in": "query" }, { "type": "integer", "description": "分页偏移,缺省 0", "name": "offset", "in": "query" }, { "enum": [ "count", "name" ], "type": "string", "description": "排序维度,缺省 count", "name": "sort", "in": "query" }, { "enum": [ "asc", "desc" ], "type": "string", "description": "排序方向;count 缺省 desc,name 缺省 asc", "name": "order", "in": "query" } ], "responses": { "200": { "description": "成功返回聚合结果 {field, facets:[{value,count,cover_url}], total, limit, offset}", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "缺少或不支持的 field", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/ids": { "get": { "security": [ { "BearerAuth": [] } ], "description": "与 /songs 共享过滤条件,仅返回 ID。用于「全选当前筛选范围」场景。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取匹配歌曲的 ID 列表", "parameters": [ { "type": "string", "description": "歌曲类型", "name": "type", "in": "query" }, { "type": "string", "description": "搜索关键词", "name": "keyword", "in": "query" }, { "type": "string", "description": "按 file_path 前缀过滤", "name": "path_prefix", "in": "query" }, { "type": "string", "description": "按流派精确过滤", "name": "genre", "in": "query" }, { "type": "string", "description": "按歌手精确过滤", "name": "artist", "in": "query" }, { "type": "string", "description": "按专辑精确过滤", "name": "album", "in": "query" }, { "type": "string", "description": "按语种精确过滤", "name": "language", "in": "query" }, { "type": "string", "description": "按风格精确过滤", "name": "style", "in": "query" }, { "type": "integer", "description": "按发行年份精确过滤", "name": "year", "in": "query" }, { "type": "integer", "description": "按年代过滤(起始年,如 1990 匹配 1990-1999)", "name": "decade", "in": "query" }, { "type": "string", "default": "hidden", "description": "排除属于这些 label 歌单的歌曲(逗号分隔), 默认 hidden; 传 none 显示全部", "name": "exclude_playlist_labels", "in": "query" }, { "enum": [ "id", "title", "artist", "album", "duration", "added_at", "updated_at", "file_modified_at", "year", "genre" ], "type": "string", "description": "排序字段,缺省 added_at", "name": "sort", "in": "query" }, { "enum": [ "asc", "desc" ], "type": "string", "description": "排序方向,缺省 desc", "name": "order", "in": "query" } ], "responses": { "200": { "description": "成功返回 ID 列表", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/names": { "get": { "security": [ { "BearerAuth": [] } ], "description": "一次性返回曲库中指定维度的全部去重、非空取值(按名称升序),不分页、无计数、无封面等冗余字段。\n支持维度:title(歌名)/artist(歌手名)。供 TV 等第三方客户端拉取曲库名录后本地搜索匹配,替代「分页拉全部歌曲再自行去重」的浪费。\nartist 按曲库原始整串返回(不按 / 、等分隔符拆分),返回的名字可直接回填 /songs?artist=\u003cvalue\u003e 精确过滤。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "曲库歌名/歌手名清单", "parameters": [ { "enum": [ "title", "artist" ], "type": "string", "description": "维度", "name": "field", "in": "query", "required": true } ], "responses": { "200": { "description": "成功返回 {field, names:[...], total}", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "缺少或不支持的 field", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/radio": { "post": { "security": [ { "BearerAuth": [] } ], "description": "批量添加电台/广播到数据库", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "批量添加电台/广播", "parameters": [ { "description": "电台/广播列表", "name": "request", "in": "body", "required": true, "schema": { "type": "array", "items": { "type": "object", "properties": { "cover_url": { "type": "string" }, "is_video": { "type": "boolean" }, "title": { "type": "string" }, "url": { "type": "string" } } } } } ], "responses": { "201": { "description": "添加成功", "schema": { "type": "object", "properties": { "count": { "type": "integer" }, "songs": { "type": "array", "items": { "$ref": "#/definitions/models.Song" } } } } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "添加失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/refresh-metadata": { "post": { "security": [ { "BearerAuth": [] } ], "description": "对所有元数据缺失且本地有文件的歌曲(本地歌曲及已缓存的网络歌曲)从文件提取时长、比特率、采样率、格式及标签并回填。未缓存的网络歌曲不参与,其元数据在播放缓存落盘后自动回填。已在运行时返回 409。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "刷新歌曲元数据", "responses": { "202": { "description": "已启动", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "409": { "description": "已在运行", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "启动失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/refresh-metadata/cancel": { "post": { "security": [ { "BearerAuth": [] } ], "description": "取消正在执行的远程歌曲元数据刷新任务", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "取消元数据刷新", "responses": { "204": { "description": "已取消" } } } }, "/songs/refresh-metadata/progress": { "get": { "security": [ { "BearerAuth": [] } ], "description": "轮询远程歌曲元数据刷新的执行状态和进度", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取元数据刷新进度", "responses": { "200": { "description": "进度信息", "schema": { "$ref": "#/definitions/services.MetadataRefreshProgress" } } } } }, "/songs/remote": { "post": { "security": [ { "BearerAuth": [] } ], "description": "批量添加网络歌曲到数据库。cover_url 支持以 \"/\" 开头的相对路径(插件场景下由服务端自动解析为内部 URL,与歌词 lyric_remote_url 的解析机制一致)。lyric_remote_url 为歌词远程 URL 直传字段,提供时优先于 lyric + lyric_source=url 的间接方式。副作用:插入成功后,对缺失技术元数据(duration/bitrate/samplerate/format)的歌曲异步探测补齐(限并发后台执行,不阻塞响应),确保 WebDAV 等无法自带时长的音源在首次播放前就落库 duration,供音箱等仅依赖服务端时长的消费端自动切歌。", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "批量添加网络歌曲", "parameters": [ { "description": "网络歌曲列表", "name": "request", "in": "body", "required": true, "schema": { "type": "array", "items": { "type": "object", "properties": { "album": { "type": "string" }, "artist": { "type": "string" }, "cover_url": { "type": "string" }, "dedup_key": { "type": "string" }, "duration": { "type": "number" }, "is_video": { "type": "boolean" }, "lyric": { "type": "string" }, "lyric_remote_url": { "type": "string" }, "lyric_source": { "type": "string" }, "plugin_entry_path": { "type": "string" }, "source_data": { "type": "string" }, "title": { "type": "string" }, "url": { "type": "string" } } } } } ], "responses": { "201": { "description": "添加成功", "schema": { "type": "object", "properties": { "count": { "type": "integer" }, "songs": { "type": "array", "items": { "$ref": "#/definitions/models.Song" } } } } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "添加失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "根据歌曲ID获取详细信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取单个歌曲详情", "parameters": [ { "type": "integer", "description": "歌曲ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "成功返回歌曲详情", "schema": { "$ref": "#/definitions/models.Song" } }, "400": { "description": "无效的歌曲ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "put": { "security": [ { "BearerAuth": [] } ], "description": "更新歌曲信息(仅支持网络歌曲和电台)", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "更新歌曲信息", "parameters": [ { "type": "integer", "description": "歌曲ID", "name": "id", "in": "path", "required": true }, { "description": "歌曲信息", "name": "request", "in": "body", "required": true, "schema": { "type": "object", "properties": { "album": { "type": "string" }, "artist": { "type": "string" }, "cover_url": { "type": "string" }, "is_live": { "type": "boolean" }, "is_video": { "type": "boolean" }, "title": { "type": "string" }, "url": { "type": "string" } } } } ], "responses": { "200": { "description": "更新成功", "schema": { "$ref": "#/definitions/models.Song" } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "更新失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } }, "delete": { "security": [ { "BearerAuth": [] } ], "description": "根据歌曲ID删除歌曲。设置 delete_files=true 时同步删除本地音频文件", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "删除歌曲", "parameters": [ { "type": "integer", "description": "歌曲ID", "name": "id", "in": "path", "required": true }, { "type": "boolean", "description": "是否同时删除本地音频文件", "name": "delete_files", "in": "query" } ], "responses": { "200": { "description": "删除成功", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "400": { "description": "无效的歌曲ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "删除失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/activate": { "post": { "security": [ { "BearerAuth": [] } ], "description": "客户端切歌前调用,让后端 cancel 同一会话下其他歌曲的进行中工作(prefetch/transcode/reassign)。其他客户端会话不受影响。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "标记当前活跃歌曲", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true } ], "responses": { "204": { "description": "无内容" }, "400": { "description": "无效的 song_id", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/audio-tracks": { "get": { "security": [ { "BearerAuth": [] } ], "description": "用 ffprobe 探测该歌曲文件的音频流,返回每条流的 audio-relative index(对应 ffmpeg -map 0:a:N)、title、language、codec、default。主要用于 Web 端双音轨(原唱/伴奏 mka)切换:前端据 tracks 数量决定是否显示切轨入口,并用 index 调 /songs/{id}/play?track=N 抽轨播放。仅本地歌曲(或已落地缓存的网络歌曲)有文件可探测;无可探测文件或音频流 \u003c 2 条时也正常返回(前端据此不显示切轨)。运行时按需探测,不落库。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取歌曲音频流列表", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "音频流列表", "schema": { "$ref": "#/definitions/handlers.audioTracksResponse" } }, "400": { "description": "无效的歌曲 ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/cover": { "get": { "security": [ { "BearerAuth": [] } ], "description": "根据歌曲 ID 获取封面图片。优先使用本地封面文件(CoverPath),其次代理 CoverURL。CoverURL 支持以 \"/\" 开头的相对路径,服务端自动经 InternalURLResolver 解析为内部 URL(含 access_token),用于插件歌曲封面代理。可选 query 参数 w:把本地封面等比缩放到该宽度(物理像素,绝不放大、上限 1024)后以 JPEG 返回,用于 Web 端降低 GPU 纹理体积(songloft-org/songloft#309);缺省或非法时返回原图。缩略仅作用于本地封面,远程代理封面忽略 w。", "produces": [ "image/jpeg" ], "tags": [ "歌曲管理" ], "summary": "获取歌曲封面图片", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "integer", "description": "本地封面缩略目标宽度(物理像素,绝不放大,上限 1024)", "name": "w", "in": "query" } ], "responses": { "200": { "description": "封面图片", "schema": { "type": "file" } }, "400": { "description": "无效的歌曲 ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲或封面不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "服务器错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/hls/playlist": { "get": { "security": [ { "BearerAuth": [] } ], "description": "HLS 反代开启时,由 ServeProxy 改写后的 m3u8 内回链触发。拉取上游 m3u8 → 同源校验 → 改写 URI → 回写给 player。", "produces": [ "application/vnd.apple.mpegurl" ], "tags": [ "电台与 HLS" ], "summary": "反代 HLS 子层 m3u8", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "string", "description": "上游 m3u8 URL(base64url 编码)", "name": "u", "in": "query", "required": true } ], "responses": { "200": { "description": "改写后的 m3u8 文本", "schema": { "type": "string" } }, "400": { "description": "song_id 或 u 参数无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "403": { "description": "非同源 URL 拒绝代理(SSRF 防护)", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "string" } }, "502": { "description": "上游不可用", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/hls/segment": { "get": { "security": [ { "BearerAuth": [] } ], "description": "由 HandlePlaylist 改写后的相对路径触发,反代音频切片、加密 key、init 段等二进制资源。透传 Range 请求。", "produces": [ "application/octet-stream" ], "tags": [ "电台与 HLS" ], "summary": "反代 HLS 切片 / key / init 段", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "string", "description": "上游切片 URL(base64url 编码)", "name": "u", "in": "query", "required": true } ], "responses": { "200": { "description": "切片二进制内容", "schema": { "type": "file" } }, "206": { "description": "Range 请求的部分内容", "schema": { "type": "file" } }, "400": { "description": "song_id 或 u 参数无效", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "403": { "description": "非同源 URL 拒绝代理", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "string" } }, "502": { "description": "上游不可用", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/lyric": { "get": { "security": [ { "BearerAuth": [] } ], "description": "根据 song.ID 返回 LyricPayload JSON,含 lyric/tlyric/rlyric/lxlyric。优先级:旁挂 .lrc 文件 \u003e DB url \u003e DB payload \u003e 歌词搜索插件。manual 歌词不被旁挂覆盖。传 refresh=1 时强制重新抓取:跳过库中自动获取的旧歌词(空/scraped/cached)重跑歌词搜索插件,响应挂 no-store 不缓存;file/embedded/manual 等权威歌词不被覆盖。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "获取歌曲歌词", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "boolean", "description": "为 true 时绕过缓存强制重新抓取歌词(重跑歌词搜索插件,不覆盖 file/embedded/manual 歌词)", "name": "refresh", "in": "query" } ], "responses": { "200": { "description": "LyricPayload", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "歌曲或歌词不存在", "schema": { "type": "string" } }, "502": { "description": "歌词获取失败", "schema": { "type": "string" } } } } }, "/songs/{id}/lyrics": { "put": { "security": [ { "BearerAuth": [] } ], "description": "更新指定歌曲的歌词内容和来源。url 来源传 lyric_remote_url,其它来源传 lyric/tlyric/rlyric/lxlyric 四字段。响应里的 file_write_status 表示是否把元数据回写到本地音频文件:written=已写入,unchanged=未变更(非本地歌曲/无文件路径/不支持的扩展名/url 来源),skipped=标签已一致无需写入,failed=尝试写入但失败(DB 已成功)。lyric_source=manual 用于标记用户手动调整,scanner 重扫时不会覆盖", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "更新歌曲歌词", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "description": "歌词信息", "name": "request", "in": "body", "required": true, "schema": { "type": "object", "properties": { "lxlyric": { "type": "string" }, "lyric": { "type": "string" }, "lyric_remote_url": { "type": "string" }, "lyric_source": { "type": "string" }, "rlyric": { "type": "string" }, "tlyric": { "type": "string" } } } } ], "responses": { "200": { "description": "更新成功", "schema": { "type": "object", "properties": { "file_write_status": { "type": "string" }, "message": { "type": "string" } } } }, "400": { "description": "请求数据错误", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "500": { "description": "更新失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/play": { "get": { "security": [ { "BearerAuth": [] } ], "description": "按 song.ID 流式返回音频。内部根据 song.type 分发到本地文件 / 缓存下载 / 直链下载 / 电台 302。", "produces": [ "application/octet-stream" ], "tags": [ "歌曲管理" ], "summary": "流式播放歌曲", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "string", "description": "目标转码格式(如 mp3、ogg),用于平台兼容性转码", "name": "format", "in": "query" }, { "type": "string", "description": "目标音质码率(128/192/320),不传或不合法值表示原始音质。指定后默认转码为 mp3(除非同时指定了 format)", "name": "quality", "in": "query" }, { "type": "integer", "description": "抽取指定音频流播放(audio-relative 0-based,对应 ffmpeg -map 0:a:N)。用于 Web 端双音轨(原唱/伴奏 mka)切轨:后端抽出单条音轨,AAC 编码时无损 remux 成 m4a、否则转 mp3。缺省/负数=不抽轨;与 media=video 互斥", "name": "track", "in": "query" }, { "type": "string", "description": "传 1 时异步预热缓存/转码,立即返回 202", "name": "prefetch", "in": "query" }, { "type": "string", "description": "传 video 时按视频播放:直出原容器(忽略 format/quality 转码,避免 -vn 丢画面),并按容器真实类型返回 Content-Type(如 video/mp4)。用于应用内视频画面渲染与 DLNA 视频投屏", "name": "media", "in": "query" }, { "type": "string", "description": "仅电台(HLS)有效。传 direct 时强制 302 直连源站、绕过本机 HLS 反代(即使 /settings/hls-proxy 已开)。原生 player 无 CORS 限制,直连可避免直播切片经反代往返后过期(404);浏览器不传此参数以继续走反代解决 CORS", "name": "hls", "in": "query" }, { "type": "string", "description": "传 1 时启用 EBU R128 音量均衡(ffmpeg loudnorm=I=-16:LRA=11:TP=-1.5),用于消除不同音源之间的响度落差。需要重编码,未同时指定 format 时默认转为 mp3。均衡产物有独立缓存(文件名带 norm. 标记);产物尚未生成时服务端边转边发一条 chunked MP3 流(无 Content-Length、不可 Range、Cache-Control 为 no-store),因此首字节不必等整首转完。media=video 忽略(-vn 会丢画面);缺 ffmpeg 时优雅降级为原始音频", "name": "normalize", "in": "query" }, { "type": "string", "description": "仅电台有效。传目标格式(如 mp3)时,服务端用 ffmpeg 把电台流实时转码为该格式(HLS 与裸流均适用)。用于只支持 MP3、无法解码 AAC/HE-AAC 或不支持 HLS 的音箱。缺 ffmpeg 或坏源时优雅降级为原样代理/302。与 format 分离:电台侧忽略 format,只认此参数", "name": "radio_transcode", "in": "query" }, { "type": "number", "description": "从第 N 秒起播。面向不支持 HTTP Range seek 的推流客户端(如小爱音箱经 player_play_url 只会从头拉流):服务端用 ffmpeg input seek 产出一条以第 N 秒为开头的 chunked MP3 流,因此响应无 Content-Length、不可 Range、Cache-Control 为 no-store;浏览器等支持 Range 的客户端请用 Range 而非此参数。仅本地歌曲与已缓存的网络歌曲有效(电台是直播、未缓存的网络歌曲会阻塞整首下载,均忽略);media=video 与 HEAD 忽略;缺 ffmpeg / seek 越过时长时优雅降级为从头完整播放", "name": "seek", "in": "query" } ], "responses": { "200": { "description": "音频文件", "schema": { "type": "file" } }, "202": { "description": "预拉取已触发", "schema": { "type": "string" } }, "302": { "description": "电台流重定向", "schema": { "type": "string" } }, "404": { "description": "歌曲不存在", "schema": { "type": "string" } }, "502": { "description": "音源不可用", "schema": { "type": "string" } } } } }, "/songs/{id}/play.m3u8": { "get": { "security": [ { "BearerAuth": [] } ], "description": "按 song.ID 流式返回音频。内部根据 song.type 分发到本地文件 / 缓存下载 / 直链下载 / 电台 302。", "produces": [ "application/octet-stream" ], "tags": [ "歌曲管理" ], "summary": "流式播放歌曲", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "string", "description": "目标转码格式(如 mp3、ogg),用于平台兼容性转码", "name": "format", "in": "query" }, { "type": "string", "description": "目标音质码率(128/192/320),不传或不合法值表示原始音质。指定后默认转码为 mp3(除非同时指定了 format)", "name": "quality", "in": "query" }, { "type": "integer", "description": "抽取指定音频流播放(audio-relative 0-based,对应 ffmpeg -map 0:a:N)。用于 Web 端双音轨(原唱/伴奏 mka)切轨:后端抽出单条音轨,AAC 编码时无损 remux 成 m4a、否则转 mp3。缺省/负数=不抽轨;与 media=video 互斥", "name": "track", "in": "query" }, { "type": "string", "description": "传 1 时异步预热缓存/转码,立即返回 202", "name": "prefetch", "in": "query" }, { "type": "string", "description": "传 video 时按视频播放:直出原容器(忽略 format/quality 转码,避免 -vn 丢画面),并按容器真实类型返回 Content-Type(如 video/mp4)。用于应用内视频画面渲染与 DLNA 视频投屏", "name": "media", "in": "query" }, { "type": "string", "description": "仅电台(HLS)有效。传 direct 时强制 302 直连源站、绕过本机 HLS 反代(即使 /settings/hls-proxy 已开)。原生 player 无 CORS 限制,直连可避免直播切片经反代往返后过期(404);浏览器不传此参数以继续走反代解决 CORS", "name": "hls", "in": "query" }, { "type": "string", "description": "传 1 时启用 EBU R128 音量均衡(ffmpeg loudnorm=I=-16:LRA=11:TP=-1.5),用于消除不同音源之间的响度落差。需要重编码,未同时指定 format 时默认转为 mp3。均衡产物有独立缓存(文件名带 norm. 标记);产物尚未生成时服务端边转边发一条 chunked MP3 流(无 Content-Length、不可 Range、Cache-Control 为 no-store),因此首字节不必等整首转完。media=video 忽略(-vn 会丢画面);缺 ffmpeg 时优雅降级为原始音频", "name": "normalize", "in": "query" }, { "type": "string", "description": "仅电台有效。传目标格式(如 mp3)时,服务端用 ffmpeg 把电台流实时转码为该格式(HLS 与裸流均适用)。用于只支持 MP3、无法解码 AAC/HE-AAC 或不支持 HLS 的音箱。缺 ffmpeg 或坏源时优雅降级为原样代理/302。与 format 分离:电台侧忽略 format,只认此参数", "name": "radio_transcode", "in": "query" }, { "type": "number", "description": "从第 N 秒起播。面向不支持 HTTP Range seek 的推流客户端(如小爱音箱经 player_play_url 只会从头拉流):服务端用 ffmpeg input seek 产出一条以第 N 秒为开头的 chunked MP3 流,因此响应无 Content-Length、不可 Range、Cache-Control 为 no-store;浏览器等支持 Range 的客户端请用 Range 而非此参数。仅本地歌曲与已缓存的网络歌曲有效(电台是直播、未缓存的网络歌曲会阻塞整首下载,均忽略);media=video 与 HEAD 忽略;缺 ffmpeg / seek 越过时长时优雅降级为从头完整播放", "name": "seek", "in": "query" } ], "responses": { "200": { "description": "音频文件", "schema": { "type": "file" } }, "202": { "description": "预拉取已触发", "schema": { "type": "string" } }, "302": { "description": "电台流重定向", "schema": { "type": "string" } }, "404": { "description": "歌曲不存在", "schema": { "type": "string" } }, "502": { "description": "音源不可用", "schema": { "type": "string" } } } } }, "/songs/{id}/played": { "post": { "security": [ { "BearerAuth": [] } ], "description": "客户端在歌曲开始播放、播放完成或被跳过时调用此端点,后端将事件广播给已订阅播放事件的 JS 插件(通过 songloft.events.onPlayEvent 注册)。source 参数标识调用来源,如 songloft-player(官方客户端)、miot(小爱音箱插件)等。type 参数标识事件类型:play(开始播放)、finish(播放完成)、skip(用户跳过)。\n副作用:当 type=play 且同时传入合法的 context_type + context_key 时,额外把该歌曲写入对应播放上下文的播放历史(见 GET /play-history),同一上下文内按歌曲去重、只保留最近 50 条。仅 type=play 会落库:finish 是同一首歌的重复信息,而 skip 上报的是上一首歌、此时上下文可能已切换,会记错归属。落库失败只记日志,不影响响应码。", "produces": [ "application/json" ], "tags": [ "歌曲管理" ], "summary": "通知歌曲播放事件", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "string", "description": "调用来源标识,如 songloft-player、miot", "name": "source", "in": "query" }, { "enum": [ "play", "finish", "skip" ], "type": "string", "description": "事件类型:play、finish、skip,默认 finish", "name": "type", "in": "query" }, { "enum": [ "playlist", "artist", "album", "genre", "year", "decade", "language", "style" ], "type": "string", "description": "播放上下文类型,仅 type=play 时生效:playlist 或分面维度(artist/album/genre/year/decade/language/style)", "name": "context_type", "in": "query" }, { "type": "string", "description": "播放上下文标识,仅 type=play 时生效:playlist 传歌单 ID,分面维度传该维度取值(如歌手名)", "name": "context_key", "in": "query" } ], "responses": { "204": { "description": "无内容" }, "400": { "description": "无效的歌曲 ID 或事件类型", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "歌曲不存在", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/songs/{id}/video-hls/playlist.m3u8": { "get": { "security": [ { "BearerAuth": [] } ], "description": "对浏览器不原生支持的视频格式(mpg/flv/wmv/rmvb/avi/mkv 等)实时转码为 HLS(H.264+AAC),返回 master.m3u8 播放列表。多音轨文件会生成 HLS 多音频 rendition(hls.js 原生支持切换)。首次请求会启动转码(转完再播);后续请求命中缓存。需要 ffmpeg。", "produces": [ "application/vnd.apple.mpegurl" ], "tags": [ "歌曲管理" ], "summary": "获取视频 HLS 播放列表", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "HLS 播放列表内容", "schema": { "type": "string" } }, "400": { "description": "无效的歌曲 ID", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "歌曲不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "503": { "description": "ffmpeg 不可用或转码失败", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/songs/{id}/video-hls/{path}": { "get": { "security": [ { "BearerAuth": [] } ], "description": "返回视频 HLS 转码生成的子播放列表或 .ts 切片文件。由 hls.js 根据 master playlist 自动请求。支持多音轨场景下的子目录结构(stream_0/playlist.m3u8, stream_0/0001.ts 等)。", "tags": [ "歌曲管理" ], "summary": "获取视频 HLS 子资源", "parameters": [ { "type": "integer", "description": "歌曲 ID", "name": "id", "in": "path", "required": true }, { "type": "string", "description": "子资源路径(如 stream_0/playlist.m3u8 或 stream_0/0001.ts)", "name": "path", "in": "path", "required": true } ], "responses": { "200": { "description": "HLS 子资源", "schema": { "type": "file" } }, "400": { "description": "无效请求", "schema": { "type": "object", "additionalProperties": { "type": "string" } } }, "404": { "description": "资源不存在", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } }, "/upgrade/check": { "get": { "security": [ { "BearerAuth": [] } ], "description": "检查是否有可用的新版本", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统升级" ], "summary": "检查更新", "responses": { "200": { "description": "成功返回更新检查结果", "schema": { "type": "object", "additionalProperties": true } }, "403": { "description": "非 Docker 环境不支持升级", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "检查更新失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/upgrade/progress": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取当前升级任务的进度信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统升级" ], "summary": "获取升级进度", "responses": { "200": { "description": "成功返回升级进度", "schema": { "$ref": "#/definitions/models.UpgradeProgress" } }, "403": { "description": "非 Docker 环境不支持升级", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/upgrade/reset": { "post": { "security": [ { "BearerAuth": [] } ], "description": "将二进制文件回退到 Docker 镜像中的原始版本,然后重启服务", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统升级" ], "summary": "回退到底包版本", "responses": { "200": { "description": "回退已开始", "schema": { "$ref": "#/definitions/models.SuccessResponse" } }, "403": { "description": "非 Docker 环境不支持回退", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/upgrade/start": { "post": { "security": [ { "BearerAuth": [] } ], "description": "开始升级到指定版本", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统升级" ], "summary": "开始升级", "parameters": [ { "description": "升级请求 {version_type: stable|dev}", "name": "request", "in": "body", "required": true, "schema": { "type": "object", "additionalProperties": { "type": "string" } } } ], "responses": { "200": { "description": "升级已开始", "schema": { "$ref": "#/definitions/models.SuccessResponse" } }, "400": { "description": "请求参数错误", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "403": { "description": "非 Docker 环境不支持升级", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "升级失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/upgrade/versions": { "get": { "security": [ { "BearerAuth": [] } ], "description": "获取正式版和测试版的版本信息", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统升级" ], "summary": "获取可用版本信息", "responses": { "200": { "description": "成功返回版本信息", "schema": { "type": "object", "additionalProperties": true } }, "403": { "description": "非 Docker 环境不支持升级", "schema": { "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "获取版本信息失败", "schema": { "$ref": "#/definitions/models.ErrorResponse" } } } } }, "/version": { "get": { "description": "获取应用的版本信息,包括版本号、Git提交哈希和构建时间", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "系统管理" ], "summary": "获取应用版本信息", "responses": { "200": { "description": "成功返回版本信息", "schema": { "type": "object", "additionalProperties": { "type": "string" } } } } } } }, "definitions": { "handlers.AutoScanSetting": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "interval_seconds": { "type": "integer" } } }, "handlers.FingerprintStatus": { "type": "object", "properties": { "auto_enabled": { "description": "AutoEnabled 扫描后是否自动计算指纹(config scan_auto_fingerprint)", "type": "boolean" }, "chromaprint_available": { "description": "ChromaprintAvailable ffmpeg 是否带 chromaprint muxer,false 时无法计算指纹", "type": "boolean" }, "computed": { "description": "Computed 已有指纹的数量", "type": "integer" }, "failed": { "description": "Failed 尝试过但失败的数量(无音轨 / 文件损坏 / 超时),不会自动重试,\n需要「重新计算全部」才会再试", "type": "integer" }, "missing": { "description": "Missing 尚未尝试过计算的数量(= total - computed - failed)", "type": "integer" }, "total": { "description": "Total 本地歌曲总数", "type": "integer" } } }, "handlers.MusicPathSetting": { "type": "object", "properties": { "auto_create_exclude_dirs": { "type": "array", "items": { "type": "string" } }, "exclude_dirs": { "type": "array", "items": { "type": "string" } }, "exclude_paths": { "type": "array", "items": { "type": "string" } }, "path": { "type": "string" } } }, "handlers.ScanRequest": { "type": "object", "properties": { "paths": { "description": "Paths 为目录级定向扫描(Issue #262):为空时扫描整个音乐根目录(默认行为);\n非空时只扫描给定目录(含子目录),过期记录清理也仅收敛到这些目录之内。\n每个目录必须位于音乐根目录之下,否则返回 400。", "type": "array", "items": { "type": "string" } }, "reimport": { "type": "boolean" } } }, "handlers.WriteSongTagsRequest": { "type": "object", "properties": { "album": { "type": "string" }, "artist": { "type": "string" }, "clear_cover": { "type": "boolean" }, "cover_data": { "type": "string" }, "cover_url": { "type": "string" }, "genre": { "type": "string" }, "language": { "type": "string" }, "lyrics": { "type": "string" }, "rename_file": { "description": "RenameFile 为 true 时按新标题重命名本地音频文件(保留原目录与扩展名),仅对本地非 CUE 歌曲生效。", "type": "boolean" }, "style": { "type": "string" }, "title": { "type": "string" }, "track": { "type": "string" }, "year": { "type": "integer" } } }, "handlers.audioTracksResponse": { "type": "object", "properties": { "tracks": { "type": "array", "items": { "$ref": "#/definitions/services.AudioTrackInfo" } } } }, "handlers.dirValidateRequest": { "type": "object", "properties": { "path": { "type": "string" } } }, "handlers.dirValidateResponse": { "type": "object", "properties": { "created": { "type": "boolean" }, "error": { "type": "string" }, "free_size": { "type": "integer" }, "total_size": { "type": "integer" }, "valid": { "type": "boolean" } } }, "handlers.equalizerSetting": { "type": "object", "properties": { "bands": { "type": "array", "items": { "type": "number" } }, "enabled": { "type": "boolean" }, "preset": { "type": "string" } } }, "handlers.githubProxySetting": { "type": "object", "properties": { "proxy": { "type": "string" } } }, "handlers.hlsProxySettingRequest": { "type": "object", "properties": { "enabled": { "type": "boolean" } } }, "handlers.httpProxySetting": { "type": "object", "properties": { "proxy": { "type": "string" } } }, "handlers.jsPluginBatchUpdateResponse": { "type": "object", "properties": { "failed": { "type": "integer" }, "message": { "type": "string" }, "results": { "type": "array", "items": { "$ref": "#/definitions/handlers.jsPluginBatchUpdateResult" } }, "skipped": { "type": "integer" }, "total": { "type": "integer" }, "updated": { "type": "integer" } } }, "handlers.jsPluginBatchUpdateResult": { "type": "object", "properties": { "current_version": { "type": "string" }, "entry_path": { "type": "string" }, "error": { "type": "string" }, "has_update": { "type": "boolean" }, "new_version": { "type": "string" }, "plugin_id": { "type": "integer" }, "plugin_name": { "type": "string" }, "success": { "type": "boolean" } } }, "handlers.jsPluginUploadResponse": { "type": "object", "properties": { "failed": { "type": "integer" }, "message": { "type": "string" }, "results": { "type": "array", "items": { "$ref": "#/definitions/handlers.jsPluginUploadResult" } }, "success": { "type": "integer" }, "total": { "type": "integer" } } }, "handlers.jsPluginUploadResult": { "type": "object", "properties": { "error": { "type": "string" }, "file_name": { "type": "string" }, "plugin": { "$ref": "#/definitions/jsplugin.JSPlugin" }, "success": { "type": "boolean" } } }, "handlers.libraryBrowseSetting": { "type": "object", "properties": { "views": { "type": "array", "items": { "$ref": "#/definitions/handlers.libraryBrowseView" } } } }, "handlers.libraryBrowseView": { "type": "object", "properties": { "key": { "type": "string" }, "visible": { "type": "boolean" } } }, "handlers.logLevelSettingRequest": { "type": "object", "properties": { "level": { "type": "string" } } }, "handlers.pluginAutoUpdateSetting": { "type": "object", "properties": { "enabled": { "type": "boolean" } } }, "handlers.pluginHealthResponse": { "type": "object", "properties": { "plugins": { "type": "array", "items": { "$ref": "#/definitions/source.PluginHealthSnapshot" } } } }, "handlers.pluginKeepAliveSetting": { "type": "object", "properties": { "plugins": { "type": "array", "items": { "type": "string" } } } }, "handlers.pluginRegistriesSetting": { "type": "object", "properties": { "registries": { "type": "array", "items": { "$ref": "#/definitions/jsplugin.RegistryConfig" } } } }, "handlers.pluginTabEntry": { "type": "object", "properties": { "entry_path": { "type": "string" }, "name": { "type": "string" }, "plugin_id": { "type": "integer" } } }, "handlers.proxyAllowlistSettingRequest": { "type": "object", "properties": { "allowlist": { "type": "array", "items": { "type": "string" } } } }, "handlers.registryInstallRequest": { "type": "object", "properties": { "download_url": { "type": "string" }, "github_proxy": { "type": "string" }, "overwrite": { "description": "Overwrite 为 true 时允许覆盖掉本地已装的同 entry_path 但不同作者的插件。\n默认 false:这种情况返回 409,由前端二次确认后带该字段重发。", "type": "boolean" }, "source_url": { "description": "SourceURL 插件所属订阅源 URL。「全部」聚合模式安装时回传:\n当未显式提供 token 时,后端据此从 plugin_registries 配置解析该源的 token。", "type": "string" }, "token": { "type": "string" } } }, "handlers.registryPluginEntry": { "type": "object", "properties": { "author": { "type": "string" }, "conflict": { "description": "Conflict 为 true 表示本地已装了同 entry_path 但**不同身份**的插件。\n此时 installed=false(这不是同一个插件),安装它会覆盖掉本地那个。", "type": "boolean" }, "conflict_with": { "description": "ConflictWith 描述占用该 entry_path 的本地插件,可直接展示给用户。", "type": "string" }, "description": { "type": "string" }, "download_url": { "type": "string" }, "entry_path": { "type": "string" }, "has_update": { "type": "boolean" }, "homepage": { "type": "string" }, "icon": { "type": "string" }, "identity": { "description": "Identity 是 entry_path 之外的身份维度(规范化 author,或 GitHub 仓库兜底)。\nentry_path 撞名时前端据 (entry_path, identity) 做稳定行标识与就地状态更新。\n为空表示无法判定身份(此时仅按 entry_path 判定)。", "type": "string" }, "installed": { "type": "boolean" }, "installed_version": { "type": "string" }, "name": { "type": "string" }, "source_name": { "description": "SourceName 该插件所属订阅源名称(仅「全部」聚合模式返回),\n供 UI 区分 entry_path 相同的两个条目。", "type": "string" }, "source_url": { "description": "SourceURL 该插件所属订阅源 URL(仅「全部」聚合模式返回),\n安装时回传给后端以按源解析私有源 token。", "type": "string" }, "version": { "type": "string" } } }, "handlers.registryRefreshRequest": { "type": "object", "properties": { "all_sources": { "type": "boolean" }, "force": { "description": "Force 为 true 时绕过服务端缓存强制重新拉取。供「刷新」按钮使用;\n翻页与搜索不应设置它,否则每翻一页都会重拉整棵注册表树。", "type": "boolean" }, "github_proxy": { "type": "string" }, "page": { "type": "integer" }, "page_size": { "type": "integer" }, "registry_url": { "type": "string" }, "search": { "type": "string" }, "token": { "type": "string" } } }, "handlers.registryRefreshResponse": { "type": "object", "properties": { "page": { "type": "integer" }, "page_size": { "type": "integer" }, "plugins": { "type": "array", "items": { "$ref": "#/definitions/handlers.registryPluginEntry" } }, "total": { "type": "integer" }, "warnings": { "type": "array", "items": { "type": "string" } } } }, "handlers.remoteTitleSourceRequest": { "type": "object", "properties": { "title_source": { "type": "string", "enum": [ "tag", "filename" ], "example": "filename" } } }, "handlers.scanAutoCreatePlaylistsRequest": { "type": "object", "properties": { "enabled": { "type": "boolean" } } }, "handlers.scanAutoFingerprintRequest": { "type": "object", "properties": { "enabled": { "type": "boolean" } } }, "handlers.scanPlaylistModeRequest": { "type": "object", "properties": { "mode": { "type": "string", "enum": [ "directory", "top_level", "bubble_up" ], "example": "directory" } } }, "handlers.scanPlaylistModeResponse": { "type": "object", "properties": { "mode": { "type": "string", "example": "directory" } } }, "handlers.scanTitleSourceRequest": { "type": "object", "properties": { "title_source": { "type": "string", "enum": [ "tag", "filename" ], "example": "tag" } } }, "handlers.startFingerprintRequest": { "type": "object", "properties": { "recompute_all": { "type": "boolean" }, "retry_failed": { "description": "RetryFailed 仅重置失败项的「已尝试」标记后重试,已算好的指纹保留。", "type": "boolean" } } }, "handlers.tabConfigSetting": { "type": "object", "properties": { "plugin_tabs": { "type": "array", "items": { "$ref": "#/definitions/handlers.pluginTabEntry" } }, "show_library": { "type": "boolean" }, "show_playlists": { "type": "boolean" } } }, "handlers.userPreferencesSetting": { "type": "object", "properties": { "audio_quality": { "type": "string" }, "local_cache_max_size": { "type": "integer" }, "play_mode": { "type": "string" }, "playlist_view_mode": { "type": "string" }, "theme_mode": { "type": "string" }, "volume": { "type": "number" } } }, "jsplugin.JSPlugin": { "type": "object", "properties": { "author": { "type": "string" }, "created_at": { "type": "string" }, "description": { "type": "string" }, "download_url": { "type": "string" }, "entry_hash": { "description": "main.js/main.jsc 内容 SHA256", "type": "string" }, "entry_path": { "description": "路由前缀(如 \"myplugin\")", "type": "string" }, "external_paths": { "description": "可访问的外部绝对路径目录", "type": "array", "items": { "type": "string" } }, "file_mod_time": { "type": "string" }, "file_path": { "description": "ZIP 文件相对路径", "type": "string" }, "homepage": { "type": "string" }, "icon": { "type": "string" }, "id": { "type": "integer" }, "license": { "type": "string" }, "main": { "description": "入口文件路径(如 \"main.js\")", "type": "string" }, "min_host_version": { "type": "string" }, "name": { "type": "string" }, "permissions": { "description": "权限列表", "type": "array", "items": { "type": "string" } }, "public_paths": { "description": "无需 JWT 认证的路径前缀", "type": "array", "items": { "type": "string" } }, "status": { "$ref": "#/definitions/models.JSPluginStatus" }, "update_url": { "type": "string" }, "updated_at": { "type": "string" }, "version": { "type": "string" }, "zip_hash": { "description": "ZIP 文件 SHA256", "type": "string" } } }, "jsplugin.RegistryConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "name": { "type": "string" }, "token": { "type": "string" }, "url": { "type": "string" } } }, "models.BatchDeletePlaylistsRequest": { "type": "object", "properties": { "delete_songs": { "description": "为 true 时,一并删除仅属于被删歌单的孤儿歌曲(含本地歌曲及其磁盘文件)", "type": "boolean", "example": false }, "ids": { "description": "要删除的歌单 ID 列表", "type": "array", "items": { "type": "integer" }, "example": [ 1 ] } } }, "models.BatchDeletePlaylistsResponse": { "type": "object", "properties": { "deleted": { "description": "实际删除的歌单数量", "type": "integer", "example": 3 }, "deleted_songs": { "description": "连带清理的孤儿歌曲数量", "type": "integer", "example": 12 } } }, "models.BatchDeleteSongsRequest": { "type": "object", "properties": { "delete_files": { "description": "是否同步删除本地音频文件", "type": "boolean", "example": false }, "ids": { "description": "要删除的歌曲 ID 列表", "type": "array", "items": { "type": "integer" }, "example": [ 1 ] } } }, "models.BatchDeleteSongsResponse": { "type": "object", "properties": { "deleted": { "description": "实际删除的数量", "type": "integer", "example": 3 } } }, "models.Config": { "type": "object", "properties": { "id": { "description": "配置ID", "type": "integer", "example": 1 }, "key": { "description": "配置键", "type": "string", "example": "music_path" }, "updated_at": { "description": "更新时间", "type": "string", "example": "2024-01-01T12:00:00Z" }, "value": { "description": "配置值(JSON格式)", "type": "string", "example": "{\"path\":\"/music\"}" } } }, "models.CreateConfigRequest": { "type": "object", "required": [ "key", "value" ], "properties": { "key": { "description": "配置键", "type": "string", "example": "music_path" }, "value": { "description": "配置值(JSON 格式)", "type": "string", "example": "{\"path\":\"/music\"}" } } }, "models.ErrorResponse": { "type": "object", "properties": { "detail": { "description": "详细错误信息(可选)", "type": "string", "example": "详细错误信息" }, "error": { "description": "错误信息", "type": "string", "example": "操作失败" } } }, "models.ImportResult": { "type": "object", "properties": { "playlists_created": { "type": "integer" }, "playlists_merged": { "type": "integer" }, "songs_created": { "type": "integer" }, "songs_matched": { "type": "integer" }, "songs_skipped": { "type": "integer" } } }, "models.JSPluginStatus": { "type": "string", "enum": [ "active", "inactive", "error" ], "x-enum-comments": { "JSPluginStatusActive": "激活状态", "JSPluginStatusError": "错误状态", "JSPluginStatusInactive": "未激活状态" }, "x-enum-descriptions": [ "激活状态", "未激活状态", "错误状态" ], "x-enum-varnames": [ "JSPluginStatusActive", "JSPluginStatusInactive", "JSPluginStatusError" ] }, "models.LoginRequest": { "type": "object", "required": [ "password", "username" ], "properties": { "password": { "description": "密码", "type": "string", "example": "admin" }, "username": { "description": "用户名", "type": "string", "example": "admin" } } }, "models.LoginResponse": { "type": "object", "properties": { "access_token": { "description": "Access Token", "type": "string", "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }, "expires_in": { "description": "Access Token 过期时间(秒)", "type": "integer", "example": 604800 }, "refresh_token": { "description": "Refresh Token", "type": "string", "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }, "token_type": { "description": "Token 类型", "type": "string", "example": "Bearer" } } }, "models.PlayHistoryEntry": { "type": "object", "properties": { "play_count": { "description": "在该上下文内的累计播放次数", "type": "integer", "example": 3 }, "played_at": { "description": "最后一次播放时间", "type": "string", "example": "2024-01-01T12:00:00Z" }, "song": { "description": "歌曲详情", "allOf": [ { "$ref": "#/definitions/models.Song" } ] } } }, "models.PlayHistoryListResponse": { "type": "object", "properties": { "items": { "description": "播放记录,按最后播放时间倒序", "type": "array", "items": { "$ref": "#/definitions/models.PlayHistoryEntry" } }, "total": { "description": "本次返回的条数(上限 50,故与 items 长度一致)", "type": "integer", "example": 12 } } }, "models.Playlist": { "type": "object", "properties": { "cover_url": { "description": "封面图片 URL", "type": "string", "example": "https://example.com/playlist.jpg" }, "created_at": { "description": "创建时间", "type": "string", "example": "2024-01-01T12:00:00Z" }, "description": { "description": "歌单描述", "type": "string", "example": "收藏的经典歌曲" }, "id": { "description": "歌单 ID", "type": "integer", "example": 1 }, "labels": { "description": "歌单标签,如 [\"built_in\"]", "type": "array", "items": { "type": "string" }, "example": [ "[\"built_in\"]" ] }, "name": { "description": "歌单名称", "type": "string", "example": "我的最爱" }, "song_count": { "description": "歌曲数量", "type": "integer", "example": 10 }, "type": { "description": "歌单类型:normal/radio", "type": "string", "enum": [ "normal", "radio" ], "example": "normal" }, "updated_at": { "description": "最后更新时间", "type": "string", "example": "2024-01-01T12:00:00Z" } } }, "models.RefreshTokenRequest": { "type": "object", "required": [ "refresh_token" ], "properties": { "refresh_token": { "description": "刷新令牌", "type": "string", "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } } }, "models.RevokeTokenRequest": { "type": "object", "properties": { "reason": { "description": "撤销原因", "type": "string", "example": "用户主动登出" } } }, "models.SetPlaylistVisibilityRequest": { "type": "object", "properties": { "hidden": { "description": "是否隐藏歌单", "type": "boolean", "example": true } } }, "models.Song": { "type": "object", "properties": { "added_at": { "description": "添加时间", "type": "string", "example": "2024-01-01T12:00:00Z" }, "album": { "description": "专辑名称", "type": "string", "example": "十一月的萧邦" }, "artist": { "description": "艺术家/歌手", "type": "string", "example": "周杰伦" }, "bit_rate": { "description": "比特率(kbps)", "type": "integer", "example": 320 }, "cover_url": { "description": "封面图片URL", "type": "string", "example": "https://example.com/cover.jpg" }, "cue_source_path": { "description": "CUE 来源路径(非空表示 CUE 拆分歌曲)", "type": "string" }, "cue_track_index": { "description": "CUE track 序号 (1-99)", "type": "integer" }, "dedup_key": { "description": "去重 key(由插件定义,典型形态 \"\u003cplatform\u003e:\u003cplatform_id\u003e\");与 PluginEntryPath 组成 UNIQUE", "type": "string" }, "duration": { "description": "播放时长(秒)", "type": "number", "example": 253.5 }, "file_modified_at": { "description": "文件修改时间(mtime,本地歌曲扫描时记录;未知为 nil)", "type": "string" }, "file_path": { "description": "本地文件路径", "type": "string", "example": "/music/周杰伦/夜曲.mp3" }, "file_size": { "description": "文件大小(字节)", "type": "integer", "example": 10485760 }, "fingerprint": { "description": "音频指纹(Chromaprint)", "type": "string" }, "fingerprint_duration": { "description": "指纹对应音频时长", "type": "number" }, "format": { "description": "音频格式", "type": "string", "example": "mp3" }, "genre": { "description": "流派", "type": "string", "example": "Pop" }, "id": { "description": "歌曲ID", "type": "integer", "example": 1 }, "is_live": { "description": "是否为直播流", "type": "boolean", "example": false }, "is_video": { "description": "是否含真实视频轨(扫描时 ffprobe 探测,排除封面);客户端据此渲染画面/选择投屏 mime", "type": "boolean", "example": false }, "isrc": { "description": "ISRC(国际标准录音编码)", "type": "string" }, "language": { "description": "语种", "type": "string", "example": "国语" }, "lyric_remote_url": { "description": "lyric_source=url 时的原始 URL(运行时由 LyricFetcher 拉取)", "type": "string" }, "lyric_url": { "description": "歌词端点 URL(客户端唯一可见字段,指向 /api/v1/songs/{id}/lyric)", "type": "string" }, "plugin_entry_path": { "description": "音源插件 entryPath(网络歌曲)", "type": "string", "example": "my-source" }, "sample_rate": { "description": "采样率(Hz)", "type": "integer", "example": 44100 }, "source_cover_url": { "description": "原始封面 URL(仅 JSON 输出,CoverURL 非空时保留原始值供编辑使用)", "type": "string" }, "source_data": { "description": "音源元数据 JSON(给插件 music/url 接口用,opaque)", "type": "string" }, "source_url": { "description": "原始音源 URL(仅 JSON 输出,radio/remote 类型返回原始流地址供编辑使用)", "type": "string" }, "style": { "description": "风格", "type": "string", "example": "抒情" }, "title": { "description": "标题", "type": "string", "example": "夜曲" }, "track": { "description": "音轨号,可为 \"3\" 或 \"3/12\"(轨号/总数)", "type": "string", "example": "3/12" }, "type": { "description": "歌曲类型:local/remote/radio", "type": "string", "enum": [ "local", "remote", "radio" ], "example": "local" }, "updated_at": { "description": "最后更新时间", "type": "string", "example": "2024-01-01T12:00:00Z" }, "url": { "description": "网络地址", "type": "string", "example": "https://example.com/song.mp3" }, "year": { "description": "发行年份", "type": "integer", "example": 2005 } } }, "models.SuccessResponse": { "type": "object", "properties": { "message": { "description": "成功信息", "type": "string", "example": "操作成功" } } }, "models.TokenInfo": { "type": "object", "properties": { "client_info": { "description": "客户端信息", "type": "string", "example": "Mozilla/5.0 AppleWebKit/605.1.15" }, "created_at": { "description": "创建时间", "type": "string", "example": "2024-01-01T12:00:00Z" }, "expires_at": { "description": "过期时间", "type": "string", "example": "2024-01-08T12:00:00Z" }, "revoked_at": { "description": "撤销时间", "type": "string", "example": "2024-01-01T12:00:00Z" }, "revoked_by": { "description": "撤销者", "type": "string", "example": "user" }, "revoked_reason": { "description": "撤销原因", "type": "string", "example": "用户主动登出" }, "token_id": { "description": "令牌 ID", "type": "string", "example": "abc123" }, "token_type": { "description": "令牌类型", "type": "string", "enum": [ "access", "refresh" ], "example": "access" } } }, "models.UpdateConfigRequest": { "type": "object", "required": [ "value" ], "properties": { "value": { "description": "配置值(JSON 格式)", "type": "string", "example": "{\"path\":\"/new_music\"}" } } }, "models.UpdatePlaylistRequest": { "type": "object", "properties": { "cover_path": { "description": "封面图片本地路径(传空字符串清除)", "type": "string", "example": "" }, "cover_song_id": { "description": "从指定歌曲复制封面(与 cover_path/cover_url 互斥,优先级更高)", "type": "integer", "example": 42 }, "cover_url": { "description": "封面图片 URL(传空字符串清除)", "type": "string", "example": "" }, "description": { "description": "歌单描述", "type": "string", "example": "收藏的经典歌曲" }, "name": { "description": "歌单名称", "type": "string", "example": "我的最爱" } } }, "models.UpgradeProgress": { "type": "object", "properties": { "current_step": { "description": "当前步骤描述", "type": "string", "example": "正在下载新版本..." }, "error": { "description": "错误信息(如有)", "type": "string", "example": "下载失败" }, "progress": { "description": "进度百分比 (0-100)", "type": "integer", "example": 50 }, "status": { "description": "状态", "type": "string", "enum": [ "idle", "downloading", "testing", "replacing", "restarting", "completed", "failed" ], "example": "downloading" } } }, "services.AudioTrackInfo": { "type": "object", "properties": { "codec": { "type": "string" }, "default": { "type": "boolean" }, "index": { "type": "integer" }, "language": { "type": "string" }, "title": { "type": "string" } } }, "services.CacheConfig": { "type": "object", "properties": { "cache_dir": { "description": "自定义缓存目录,空字符串表示使用默认目录", "type": "string" }, "max_size": { "description": "最大缓存大小(字节),0 表示无限制", "type": "integer" }, "transcode_format": { "description": "TranscodeFormat 缓存网络歌曲落盘时统一转码的目标格式(mp3/m4a/ogg/flac/wav)。\n空字符串表示不转码、按上游原格式落盘(默认)。缺 ffmpeg 或转码失败时优雅降级保留原格式。", "type": "string" }, "transcode_quality": { "description": "TranscodeQuality 转码目标码率(128/192/320),空或非法值表示最高质量。仅对有损格式生效。", "type": "string" } } }, "services.CacheConfigResponse": { "type": "object", "properties": { "cache_dir": { "type": "string" }, "default_cache_dir": { "type": "string" }, "max_size": { "type": "integer" }, "transcode_format": { "type": "string" }, "transcode_quality": { "type": "string" } } }, "services.CacheStats": { "type": "object", "properties": { "file_count": { "description": "文件数量", "type": "integer" }, "max_size": { "description": "最大缓存大小(字节),0 表示无限制", "type": "integer" }, "total_size": { "description": "总大小(字节)", "type": "integer" } } }, "services.FingerprintProgress": { "type": "object", "properties": { "computed": { "type": "integer" }, "failed": { "type": "integer" }, "status": { "description": "idle, running, done, cancelled", "type": "string" }, "total": { "type": "integer" } } }, "services.MetadataRefreshProgress": { "type": "object", "properties": { "failed": { "type": "integer" }, "processed": { "type": "integer" }, "status": { "type": "string" }, "total": { "type": "integer" } } }, "services.OrganizeItem": { "type": "object", "properties": { "id": { "type": "integer" }, "target_path": { "type": "string" } } }, "services.OrganizePreviewResult": { "type": "object", "properties": { "error": { "type": "string" }, "id": { "type": "integer" }, "new_path": { "type": "string" }, "old_path": { "type": "string" }, "status": { "type": "string" } } }, "services.OrganizeResult": { "type": "object", "properties": { "error": { "type": "string" }, "file_path": { "type": "string" }, "id": { "type": "integer" }, "status": { "type": "string" } } }, "services.RefreshResponse": { "type": "object", "properties": { "access_token": { "type": "string" }, "expires_in": { "type": "integer" }, "refresh_token": { "type": "string" }, "token_type": { "type": "string" } } }, "services.ScanProgress": { "type": "object", "properties": { "cleaned_files": { "description": "清理的过期文件数", "type": "integer" }, "cue_split_sources": { "description": "splitting_cue 阶段已处理的 CUE 来源数", "type": "integer" }, "current_file": { "description": "当前处理的文件", "type": "string" }, "discovered_files": { "description": "scanning 阶段已发现的音频文件数", "type": "integer" }, "end_time": { "description": "结束时间", "type": "string" }, "error": { "description": "错误信息", "type": "string" }, "failed_files": { "description": "失败的文件数", "type": "integer" }, "imported_files": { "description": "已导入文件数", "type": "integer" }, "local_song_count": { "description": "扫描完成后数据库中本地歌曲总数", "type": "integer" }, "scanned_files": { "description": "已扫描文件数", "type": "integer" }, "skipped_files": { "description": "跳过的文件数(已存在)", "type": "integer" }, "start_time": { "description": "开始时间", "type": "string" }, "status": { "description": "当前状态", "allOf": [ { "$ref": "#/definitions/services.ScanStatus" } ] }, "total_files": { "description": "总文件数", "type": "integer" } } }, "services.ScanStatus": { "type": "string", "enum": [ "idle", "scanning", "importing", "splitting_cue", "creating_playlists", "completed", "failed", "cancelling", "cancelled" ], "x-enum-comments": { "ScanStatusCancelled": "已取消", "ScanStatusCancelling": "取消中", "ScanStatusCompleted": "已完成", "ScanStatusCreatingPlaylists": "自动创建歌单中", "ScanStatusFailed": "失败", "ScanStatusIdle": "空闲", "ScanStatusImporting": "导入中", "ScanStatusScanning": "扫描文件中", "ScanStatusSplittingCue": "CUE 整轨切分中" }, "x-enum-descriptions": [ "空闲", "扫描文件中", "导入中", "CUE 整轨切分中", "自动创建歌单中", "已完成", "失败", "取消中", "已取消" ], "x-enum-varnames": [ "ScanStatusIdle", "ScanStatusScanning", "ScanStatusImporting", "ScanStatusSplittingCue", "ScanStatusCreatingPlaylists", "ScanStatusCompleted", "ScanStatusFailed", "ScanStatusCancelling", "ScanStatusCancelled" ] }, "source.HealthClass": { "type": "string", "enum": [ "green", "yellow", "red" ], "x-enum-comments": { "HealthGreen": "成功率 ≥ green_threshold 且 samples ≥ min_samples", "HealthRed": "成功率 \u003c red_threshold 且 samples ≥ min_samples", "HealthYellow": "介于 green 与 red 之间;或 samples 不足" }, "x-enum-descriptions": [ "成功率 ≥ green_threshold 且 samples ≥ min_samples", "介于 green 与 red 之间;或 samples 不足", "成功率 \u003c red_threshold 且 samples ≥ min_samples" ], "x-enum-varnames": [ "HealthGreen", "HealthYellow", "HealthRed" ] }, "source.Outcome": { "type": "object", "properties": { "latency_ns": { "description": "纳秒;swagger 显式声明避免 time.Duration 解析失败", "type": "integer" }, "plugin_entry_path": { "type": "string" }, "reason": { "type": "string" }, "result": { "$ref": "#/definitions/source.OutcomeResult" }, "size_bytes": { "type": "integer" }, "timestamp": { "type": "string" } } }, "source.OutcomeResult": { "type": "string", "enum": [ "success", "network_fail", "probe_fail", "validation_fail", "plugin_invocation_fail" ], "x-enum-comments": { "OutcomeNetworkFail": "HTTP 错误、超时、读流中断", "OutcomePluginInvocationFail": "插件 music/url 返回错误或非 200", "OutcomeProbeFail": "ffprobe / tag.ReadFrom 失败", "OutcomeValidationFail": "校验未通过(过短、码率过低等)" }, "x-enum-descriptions": [ "", "HTTP 错误、超时、读流中断", "ffprobe / tag.ReadFrom 失败", "校验未通过(过短、码率过低等)", "插件 music/url 返回错误或非 200" ], "x-enum-varnames": [ "OutcomeSuccess", "OutcomeNetworkFail", "OutcomeProbeFail", "OutcomeValidationFail", "OutcomePluginInvocationFail" ] }, "source.PluginHealthSnapshot": { "type": "object", "properties": { "class": { "$ref": "#/definitions/source.HealthClass" }, "last_failures": { "description": "最近 N 次失败,便于排查", "type": "array", "items": { "$ref": "#/definitions/source.Outcome" } }, "plugin_entry_path": { "type": "string" }, "samples": { "type": "integer" }, "success_rate": { "type": "number" } } } }, "securityDefinitions": { "BearerAuth": { "description": "输入 \"Bearer {token}\" 进行认证", "type": "apiKey", "name": "Authorization", "in": "header" } } }