mirror of
https://github.com/hanxi/xiaomusic.git
synced 2026-03-15 08:13:16 +08:00
feat: 网络歌单插件功能更新 (#690)
1. 增加【插件订阅源】配置及管理 2. 修复【WYY】插件无法使用bug 3. 将开放接口与插件源融合展示,方便用户指定搜索源
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -173,3 +173,4 @@ xiaomusic.log.txt*
|
||||
node_modules
|
||||
reference/
|
||||
.aone_copilot/
|
||||
.idea/
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"big-integer": "^1.6.52",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.10",
|
||||
@@ -33,6 +34,15 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.52",
|
||||
"resolved": "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz",
|
||||
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
|
||||
"license": "Unlicense",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"main": "xiaomusic/js_plugin_runner.js",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"big-integer": "^1.6.52",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.10",
|
||||
|
||||
@@ -116,11 +116,19 @@ async def upload_js_plugin(
|
||||
|
||||
plugin_dir = xiaomusic.js_plugin_manager.plugins_dir
|
||||
os.makedirs(plugin_dir, exist_ok=True)
|
||||
# 校验命名是否是保留字段【ALL/all/OpenAPI】,是的话抛错
|
||||
sys_files = ["ALL.js", "all.js", "OpenAPI.js", "OPENAPI.js"]
|
||||
if file.filename in sys_files:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=f"插件名非法,不能命名为: {sys_files} ,请修改后再上传!",
|
||||
)
|
||||
file_path = os.path.join(plugin_dir, file.filename)
|
||||
# 校验是否已存在同名js插件 存在则提示,停止上传
|
||||
if os.path.exists(file_path):
|
||||
raise HTTPException(
|
||||
status_code=409, detail=f"插件 {file.filename} 已存在,请重命名后再上传"
|
||||
status_code=409,
|
||||
detail=f"插件 {file.filename} 已存在,请重命名后再上传!",
|
||||
)
|
||||
file_path = os.path.join(plugin_dir, file.filename)
|
||||
|
||||
@@ -142,6 +150,9 @@ async def upload_js_plugin(
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
# ----------------------------开放接口相关函数---------------------------------------
|
||||
|
||||
|
||||
@router.get("/api/openapi/load")
|
||||
def get_openapi_info(Verifcation=Depends(verification)):
|
||||
"""获取开放接口配置信息"""
|
||||
@@ -172,3 +183,38 @@ async def update_openapi_url(request: Request, Verifcation=Depends(verification)
|
||||
return xiaomusic.js_plugin_manager.update_openapi_url(search_url)
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
# ----------------------------插件源接口---------------------------------------
|
||||
|
||||
|
||||
@router.get("/api/plugin-source/load")
|
||||
def get_plugin_source_info(Verifcation=Depends(verification)):
|
||||
"""获取插件源配置信息"""
|
||||
try:
|
||||
plugin_source = xiaomusic.js_plugin_manager.get_plugin_source()
|
||||
return {"success": True, "data": plugin_source}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.post("/api/plugin-source/refresh")
|
||||
def refresh_plugin_source(Verifcation=Depends(verification)):
|
||||
"""更新订阅源"""
|
||||
try:
|
||||
return xiaomusic.js_plugin_manager.refresh_plugin_source()
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.post("/api/plugin-source/updateUrl")
|
||||
async def update_plugin_source(request: Request, Verifcation=Depends(verification)):
|
||||
"""更新插件源地址"""
|
||||
try:
|
||||
request_json = await request.json()
|
||||
source_url = request_json.get("source_url")
|
||||
if not request_json or "source_url" not in request_json:
|
||||
return {"success": False, "error": "Missing 'search_url' in request body"}
|
||||
return xiaomusic.js_plugin_manager.update_plugin_source_url(source_url)
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@@ -286,6 +286,182 @@ class JSPluginManager:
|
||||
self.log.error(f"Failed to update OpenAPI config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
def get_plugin_source(self) -> dict[str, Any]:
|
||||
"""获取插件源配置信息
|
||||
Returns:
|
||||
Dict[str, Any]: 包含 OpenAPI 配置信息的字典,包括启用状态和搜索 URL
|
||||
"""
|
||||
try:
|
||||
# 读取配置文件中的 OpenAPI 配置信息
|
||||
config_data = self._get_config_data()
|
||||
if config_data:
|
||||
# 返回 openapi_info 配置项
|
||||
return config_data.get("plugin_source", {})
|
||||
else:
|
||||
return {"enabled": False}
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to read plugin source info from config: {e}")
|
||||
return {}
|
||||
|
||||
def refresh_plugin_source(self) -> dict[str, Any]:
|
||||
"""更新订阅源"""
|
||||
try:
|
||||
if os.path.exists(self.plugins_config_path):
|
||||
with open(self.plugins_config_path, encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
plugin_source = config_data.get("plugin_source", {})
|
||||
source_url = plugin_source.get("source_url", "")
|
||||
if source_url:
|
||||
import requests
|
||||
|
||||
# 请求源地址
|
||||
response = requests.get(source_url, timeout=30)
|
||||
response.raise_for_status() # 抛出HTTP错误
|
||||
# 解析响应JSON
|
||||
json_data = response.json()
|
||||
# 校验响应格式 - 检查是否包含 plugins 数组
|
||||
if not isinstance(json_data, dict) or "plugins" not in json_data:
|
||||
return {"success": False, "error": "无效订阅源!"}
|
||||
plugins_array = json_data["plugins"]
|
||||
if not isinstance(plugins_array, list):
|
||||
return {"success": False, "error": "无效订阅源!"}
|
||||
# 写入插件文本
|
||||
self.download_and_save_plugin(plugins_array)
|
||||
# 使缓存失效
|
||||
self._invalidate_config_cache()
|
||||
self.reload_plugins()
|
||||
return {"success": True}
|
||||
else:
|
||||
return {"success": False, "error": "未找到配置订阅源!"}
|
||||
else:
|
||||
return {"success": False}
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to toggle OpenAPI config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
def download_and_save_plugin(self, plugins_array: list) -> bool:
|
||||
"""下载并保存插件数组中的所有插件
|
||||
|
||||
Args:
|
||||
plugins_array: 插件信息列表,格式如 [{"name": "plugin_name", "url": "plugin_url", "version": "version"}, ...]
|
||||
|
||||
Returns:
|
||||
bool: 所有插件下载保存是否全部成功
|
||||
"""
|
||||
if not plugins_array or not isinstance(plugins_array, list):
|
||||
self.log.warning("Empty or invalid plugins array provided")
|
||||
return False
|
||||
|
||||
all_success = True
|
||||
|
||||
for plugin_info in plugins_array:
|
||||
if (
|
||||
not isinstance(plugin_info, dict)
|
||||
or "name" not in plugin_info
|
||||
or "url" not in plugin_info
|
||||
):
|
||||
self.log.warning(f"Invalid plugin entry: {plugin_info}")
|
||||
all_success = False
|
||||
continue
|
||||
|
||||
plugin_name = plugin_info["name"]
|
||||
plugin_url = plugin_info["url"]
|
||||
|
||||
if not plugin_name or not plugin_url:
|
||||
self.log.warning(f"Invalid plugin entry: {plugin_name} -> {plugin_url}")
|
||||
all_success = False
|
||||
continue
|
||||
|
||||
# 调用单个插件下载方法
|
||||
success = self.download_single_plugin(plugin_name, plugin_url)
|
||||
if not success:
|
||||
all_success = False
|
||||
self.log.error(f"Failed to download plugin: {plugin_name}")
|
||||
|
||||
return all_success
|
||||
|
||||
def download_single_plugin(self, plugin_name: str, plugin_url: str) -> bool:
|
||||
"""下载并保存单个插件
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
plugin_url: 插件下载URL
|
||||
|
||||
Returns:
|
||||
bool: 下载保存是否成功
|
||||
"""
|
||||
import requests
|
||||
|
||||
# 检查插件名称是否合法
|
||||
sys_files = ["ALL", "all", "OpenAPI", "OPENAPI"]
|
||||
if plugin_name in sys_files:
|
||||
self.log.error(f"Plugin name {plugin_name} is reserved and cannot be used")
|
||||
return False
|
||||
|
||||
# 创建插件目录
|
||||
os.makedirs(self.plugins_dir, exist_ok=True)
|
||||
|
||||
# 生成文件路径
|
||||
plugin_filename = f"{plugin_name}.js"
|
||||
file_path = os.path.join(self.plugins_dir, plugin_filename)
|
||||
|
||||
# 检查是否已存在同名插件
|
||||
if os.path.exists(file_path):
|
||||
self.log.warning(f"Plugin {plugin_name} already exists, will overwrite")
|
||||
|
||||
try:
|
||||
# 下载插件内容
|
||||
response = requests.get(plugin_url, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
# 验证下载的内容是否为有效的JS代码(简单检查是否以有意义的JS字符开头)
|
||||
content = response.text.strip()
|
||||
if not content:
|
||||
self.log.error(f"Downloaded plugin {plugin_name} has empty content")
|
||||
return False
|
||||
|
||||
# 保存插件文件
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
self.log.info(f"Successfully downloaded and saved plugin: {plugin_name}")
|
||||
|
||||
# 更新插件配置
|
||||
self.update_plugin_config(plugin_name, plugin_filename)
|
||||
return True
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.log.error(
|
||||
f"Failed to download plugin {plugin_name} from {plugin_url}: {e}"
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to save plugin {plugin_name}: {e}")
|
||||
return False
|
||||
|
||||
def update_plugin_source_url(self, source_url: str) -> dict[str, Any]:
|
||||
"""更新开放接口地址"""
|
||||
try:
|
||||
if os.path.exists(self.plugins_config_path):
|
||||
with open(self.plugins_config_path, encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
plugin_source = config_data.get("plugin_source", {})
|
||||
plugin_source["source_url"] = source_url
|
||||
config_data["plugin_source"] = plugin_source
|
||||
|
||||
with open(self.plugins_config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 使缓存失效
|
||||
self._invalidate_config_cache()
|
||||
return {"success": True}
|
||||
else:
|
||||
return {"success": False}
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to update plugin source config: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
"""----------------------------------------------------------------------"""
|
||||
|
||||
def _get_config_data(self):
|
||||
@@ -332,9 +508,12 @@ class JSPluginManager:
|
||||
base_config = {
|
||||
"account": "",
|
||||
"password": "",
|
||||
"auto_add_song": True,
|
||||
"aiapi_info": {"enabled": False, "api_key": ""},
|
||||
"enabled_plugins": [],
|
||||
"plugins_info": [],
|
||||
"openapi_info": {"enabled": False, "search_url": ""},
|
||||
"plugin_source": {"source_url": ""},
|
||||
"plugins_info": [],
|
||||
}
|
||||
with open(self.plugins_config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(base_config, f, ensure_ascii=False, indent=2)
|
||||
@@ -423,6 +602,7 @@ class JSPluginManager:
|
||||
"""刷新插件列表,强制重新加载配置数据"""
|
||||
# 强制使缓存失效,重新加载配置
|
||||
self._invalidate_config_cache()
|
||||
self.reload_plugins()
|
||||
# 返回最新的插件列表
|
||||
return self.get_plugin_list()
|
||||
|
||||
@@ -464,7 +644,13 @@ class JSPluginManager:
|
||||
# 读取配置文件中的启用插件列表
|
||||
config_data = self._get_config_data()
|
||||
if config_data:
|
||||
return config_data.get("enabled_plugins", [])
|
||||
enabled_plugins = config_data.get("enabled_plugins", [])
|
||||
# 追加开放接口名称
|
||||
openapi_info = config_data.get("openapi_info", {})
|
||||
enabled_openapi = openapi_info.get("enabled", False)
|
||||
if enabled_openapi and "OpenAPI" not in enabled_plugins:
|
||||
enabled_plugins.insert(0, "OpenAPI")
|
||||
return enabled_plugins
|
||||
else:
|
||||
return []
|
||||
except Exception as e:
|
||||
@@ -554,7 +740,8 @@ class JSPluginManager:
|
||||
# 构造请求参数
|
||||
params = {"type": "aggregateSearch", "keyword": keyword, "limit": limit}
|
||||
# 使用aiohttp发起异步HTTP GET请求
|
||||
async with aiohttp.ClientSession() as session:
|
||||
connector = aiohttp.TCPConnector(ssl=False) # 跳过 SSL 验证
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.get(
|
||||
url, params=params, timeout=aiohttp.ClientTimeout(total=10)
|
||||
) as response:
|
||||
@@ -667,7 +854,6 @@ class JSPluginManager:
|
||||
|
||||
# 获取待处理的数据列表
|
||||
data_list = result_data["data"]
|
||||
self.log.info(f"列表信息::{data_list}")
|
||||
# 预计算平台权重,启用插件列表中的前9个插件有权重,排名越靠前权重越高
|
||||
enabled_plugins = self.get_enabled_plugins()
|
||||
plugin_weights = {p: 9 - i for i, p in enumerate(enabled_plugins[:9])}
|
||||
@@ -708,8 +894,11 @@ class JSPluginManager:
|
||||
artist_score = 800
|
||||
elif ar in artist:
|
||||
artist_score = 600
|
||||
|
||||
platform_bonus = plugin_weights.get(platform, 0)
|
||||
# 开放接口的平台权重最高 20
|
||||
if platform.startswith("OpenAPI-"):
|
||||
platform_bonus = 20
|
||||
else:
|
||||
platform_bonus = plugin_weights.get(platform, 0)
|
||||
return title_score + artist_score + platform_bonus
|
||||
|
||||
sorted_data = sorted(data_list, key=calculate_match_score, reverse=True)
|
||||
@@ -1120,7 +1309,7 @@ class JSPluginManager:
|
||||
self.plugins.clear()
|
||||
# 重新加载插件
|
||||
self._load_plugins()
|
||||
self.log.info("Plugins reloaded successfully")
|
||||
self.log.info(f"最新插件信息:{self.plugins}")
|
||||
|
||||
def update_plugin_config(self, plugin_name: str, plugin_file: str):
|
||||
"""更新插件配置文件"""
|
||||
|
||||
3
xiaomusic/js_plugin_runner.js
vendored
3
xiaomusic/js_plugin_runner.js
vendored
@@ -195,7 +195,8 @@ class PluginRunner {
|
||||
'he': require('he'),
|
||||
'dayjs': require('dayjs'),
|
||||
'cheerio': require('cheerio'),
|
||||
'qs': require('qs')
|
||||
'qs': require('qs'),
|
||||
'big-integer': require('big-integer')
|
||||
};
|
||||
|
||||
const safeRequire = (moduleName) => {
|
||||
|
||||
@@ -78,14 +78,85 @@ class OnlineMusicService:
|
||||
if not self.js_plugin_manager:
|
||||
return {"success": False, "error": "JS Plugin Manager not available"}
|
||||
|
||||
# 初始化 artist 变量
|
||||
artist = ""
|
||||
# 解析关键词,可能通过AI或直接分割
|
||||
parsed_keyword, parsed_artist = await self._parse_keyword_with_ai(keyword)
|
||||
keyword = parsed_keyword or keyword
|
||||
artist = parsed_artist or artist
|
||||
# 解析关键词和艺术家
|
||||
keyword, artist = await self._parse_keyword_and_artist(keyword)
|
||||
|
||||
# 获取API配置信息
|
||||
openapi_info = self.js_plugin_manager.get_openapi_info()
|
||||
|
||||
if plugin == "all":
|
||||
# 并发执行插件搜索和OpenAPI搜索
|
||||
return await self._execute_concurrent_searches(
|
||||
keyword, artist, page, limit, openapi_info
|
||||
)
|
||||
elif plugin == "OpenAPI":
|
||||
# OpenAPI搜索
|
||||
return await self._execute_openapi_search(openapi_info, keyword, artist)
|
||||
else:
|
||||
# 插件在线搜索
|
||||
return await self._execute_plugin_search(
|
||||
plugin, keyword, artist, page, limit
|
||||
)
|
||||
|
||||
async def _parse_keyword_and_artist(self, keyword):
|
||||
"""解析关键词和艺术家"""
|
||||
parsed_keyword, parsed_artist = await self._parse_keyword_with_ai(keyword)
|
||||
keyword = parsed_keyword or keyword
|
||||
artist = parsed_artist or ""
|
||||
return keyword, artist
|
||||
|
||||
async def _execute_concurrent_searches(
|
||||
self, keyword, artist, page, limit, openapi_info
|
||||
):
|
||||
"""执行并发搜索 - 插件和OpenAPI"""
|
||||
tasks = []
|
||||
|
||||
# 插件在线搜索任务
|
||||
plugin_task = asyncio.create_task(
|
||||
self.get_music_list_mf(
|
||||
"all", keyword=keyword, artist=artist, page=page, limit=limit
|
||||
)
|
||||
)
|
||||
tasks.append(plugin_task)
|
||||
|
||||
# OpenAPI搜索任务(只有在配置正确时才创建)
|
||||
if (
|
||||
openapi_info.get("enabled", False)
|
||||
and openapi_info.get("search_url", "") != ""
|
||||
):
|
||||
openapi_task = asyncio.create_task(
|
||||
self.js_plugin_manager.openapi_search(
|
||||
url=openapi_info.get("search_url"), keyword=keyword, artist=artist
|
||||
)
|
||||
)
|
||||
tasks.append(openapi_task)
|
||||
|
||||
# 并发执行任务
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
plugin_result = results[0]
|
||||
openapi_result = results[1] if len(results) > 1 else None
|
||||
|
||||
# 处理异常情况
|
||||
plugin_result = self._handle_search_exception(plugin_result, "插件")
|
||||
openapi_result = self._handle_search_exception(openapi_result, "OpenAPI")
|
||||
|
||||
# 合并结果
|
||||
combined_result = self._merge_search_results(
|
||||
plugin_result, openapi_result, keyword, artist, limit
|
||||
)
|
||||
combined_result["artist"] = artist or "佚名"
|
||||
return combined_result
|
||||
|
||||
def _handle_search_exception(self, result, source_name):
|
||||
"""处理搜索异常"""
|
||||
if result and isinstance(result, Exception):
|
||||
self.log.error(f"{source_name}搜索发生异常: {result}")
|
||||
return {"success": False, "error": str(result)}
|
||||
return result
|
||||
|
||||
async def _execute_openapi_search(self, openapi_info, keyword, artist):
|
||||
"""执行OpenAPI搜索"""
|
||||
if (
|
||||
openapi_info.get("enabled", False)
|
||||
and openapi_info.get("search_url", "") != ""
|
||||
@@ -94,17 +165,71 @@ class OnlineMusicService:
|
||||
result_data = await self.js_plugin_manager.openapi_search(
|
||||
url=openapi_info.get("search_url"), keyword=keyword, artist=artist
|
||||
)
|
||||
result_data["isOpenAPI"] = True
|
||||
else:
|
||||
# 插件在线搜索
|
||||
result_data = await self.get_music_list_mf(
|
||||
plugin, keyword=keyword, artist=artist, page=page, limit=limit
|
||||
)
|
||||
result_data["isOpenAPI"] = False
|
||||
# 将歌手名当作附加值,用于歌手搜索
|
||||
return {"success": False, "error": "OpenAPI未启用或配置错误"}
|
||||
|
||||
result_data["artist"] = artist or "佚名"
|
||||
return result_data
|
||||
|
||||
async def _execute_plugin_search(self, plugin, keyword, artist, page, limit):
|
||||
"""执行插件搜索"""
|
||||
result_data = await self.get_music_list_mf(
|
||||
plugin, keyword=keyword, artist=artist, page=page, limit=limit
|
||||
)
|
||||
result_data["artist"] = artist or "佚名"
|
||||
return result_data
|
||||
|
||||
def _merge_search_results(
|
||||
self, plugin_result, openapi_result, keyword, artist, limit
|
||||
):
|
||||
merged_data = []
|
||||
sources = {}
|
||||
|
||||
# 先处理 OpenAPI 结果
|
||||
if openapi_result and openapi_result.get("success"):
|
||||
openapi_data = openapi_result.get("data", [])
|
||||
if openapi_data:
|
||||
for item in openapi_data:
|
||||
item["source"] = "openapi"
|
||||
merged_data.extend(openapi_data)
|
||||
if "sources" in openapi_result:
|
||||
sources.update(openapi_result["sources"])
|
||||
|
||||
# 再处理插件结果
|
||||
if plugin_result and plugin_result.get("success"):
|
||||
plugin_data = plugin_result.get("data", [])
|
||||
if plugin_data:
|
||||
for item in plugin_data:
|
||||
item["source"] = "plugin"
|
||||
merged_data.extend(plugin_data)
|
||||
sources.update(plugin_result.get("sources", {}))
|
||||
|
||||
# 如果都没有成功结果,返回错误
|
||||
if not plugin_result.get("success") and not (
|
||||
openapi_result and openapi_result.get("success")
|
||||
):
|
||||
# 优先返回第一个错误
|
||||
error_result = (
|
||||
plugin_result if not plugin_result.get("success") else openapi_result
|
||||
)
|
||||
return error_result
|
||||
|
||||
# 优化合并后的结果
|
||||
optimized_result = self.js_plugin_manager.optimize_search_results(
|
||||
{"data": merged_data},
|
||||
search_keyword=keyword,
|
||||
limit=limit,
|
||||
search_artist=artist,
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": optimized_result.get("data", []),
|
||||
"total": len(optimized_result.get("data", [])),
|
||||
"sources": sources,
|
||||
"merged": True, # 标识这是合并结果
|
||||
}
|
||||
|
||||
async def get_music_list_mf(
|
||||
self, plugin="all", keyword="", artist="", page=1, limit=20, **kwargs
|
||||
):
|
||||
@@ -216,7 +341,8 @@ class OnlineMusicService:
|
||||
song_name = result.get("name", "")
|
||||
artist = result.get("artist", "")
|
||||
# 构建新的关键词
|
||||
keyword = _build_keyword(song_name, artist)
|
||||
# keyword = _build_keyword(song_name, artist)
|
||||
keyword = song_name
|
||||
self.log.info(f"AI提取到的信息: {result}")
|
||||
return keyword, artist
|
||||
|
||||
@@ -725,9 +851,12 @@ class OnlineMusicService:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@staticmethod
|
||||
async def get_real_url_of_openapi(url: str, timeout: int = 10) -> str:
|
||||
async def _make_request_with_validation(
|
||||
url: str, timeout: int, convert_m4s: bool = False
|
||||
) -> str:
|
||||
"""
|
||||
通过服务端代理获取开放接口真实的音乐播放URL,避免CORS问题
|
||||
通用的URL请求和验证方法
|
||||
|
||||
Args:
|
||||
url (str): 原始音乐URL
|
||||
timeout (int): 请求超时时间(秒)
|
||||
|
||||
2
xiaomusic/static/onlineSearch/config.js
vendored
2
xiaomusic/static/onlineSearch/config.js
vendored
@@ -1,6 +1,6 @@
|
||||
// config.js
|
||||
window.appConfig = {
|
||||
// TODO 版本号
|
||||
version: "1.0.4",
|
||||
version: "1.0.5",
|
||||
// 其他配置项可继续添加
|
||||
};
|
||||
|
||||
BIN
xiaomusic/static/onlineSearch/favicon.ico
Normal file
BIN
xiaomusic/static/onlineSearch/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
7
xiaomusic/static/onlineSearch/index.html
vendored
7
xiaomusic/static/onlineSearch/index.html
vendored
@@ -2,6 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">
|
||||
<title>在线音乐搜索</title>
|
||||
<!-- ... 公共配置 ... -->
|
||||
@@ -853,7 +854,6 @@
|
||||
let currentLyrics = []; // 存储解析后的歌词
|
||||
let lyricLines = []; // 存储歌词DOM元素
|
||||
let songList = []; // 存储搜索到的歌曲列表
|
||||
let isOpenAPI = false; // 是否为开放接口
|
||||
|
||||
// 页面加载时获取插件列表
|
||||
window.onload = function() {
|
||||
@@ -981,7 +981,6 @@
|
||||
const response = await fetch(`/api/search/online?keyword=${encodeURIComponent(keyword)}&plugin=${plugin}`);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
isOpenAPI = data.isOpenAPI;
|
||||
displayResults(data.data);
|
||||
} else {
|
||||
resultsDiv.innerHTML = `<div class="error">搜索失败: ${data.error}</div>`;
|
||||
@@ -1117,7 +1116,7 @@
|
||||
async function webPlayMusic(mediaItem) {
|
||||
try {
|
||||
let playUrl;
|
||||
if (mediaItem && isOpenAPI) {
|
||||
if (mediaItem && mediaItem.isOpenAPI) {
|
||||
// playUrl = await sniffRealMusicUrl(mediaItem.url);
|
||||
playUrl = mediaItem.url;
|
||||
} else {
|
||||
@@ -1248,7 +1247,7 @@
|
||||
}
|
||||
try {
|
||||
let lrcText
|
||||
if (mediaItem && isOpenAPI) {//在线接口
|
||||
if (mediaItem && mediaItem.isOpenAPI) {//在线接口
|
||||
// 调用OpenApi接口 GET获取歌词
|
||||
const response = await fetch(mediaItem.lrc, {
|
||||
method: 'GET',
|
||||
|
||||
591
xiaomusic/static/onlineSearch/setting.html
vendored
591
xiaomusic/static/onlineSearch/setting.html
vendored
@@ -2,26 +2,220 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>在线搜索配置</title>
|
||||
<!-- ... 公共配置 ... -->
|
||||
<script src="./config.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.version-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 调整 header 内容的 padding,避免与版本信息重叠 */
|
||||
.header {
|
||||
background: #31c27c;
|
||||
color: white;
|
||||
padding: 20px 20px 20px 60px; /* 左边增加留白 */
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #d32f2f;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
/* 插件设置专用样式 */
|
||||
.plugins-section {
|
||||
padding: 20px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
/*加粗*/
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.plugin-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
.plugin-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
}
|
||||
.plugin-info {
|
||||
flex: 1;
|
||||
}
|
||||
.plugin-name {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.plugin-status {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 5px; /* 添加上边距 */
|
||||
}
|
||||
.status-enabled {
|
||||
background: #e8f5e9;
|
||||
color: #4caf50;
|
||||
}
|
||||
.status-disabled {
|
||||
background: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
.plugin-details {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.plugin-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.action-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.enable-btn {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
.enable-btn:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
.disable-btn {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
.disable-btn:hover {
|
||||
background: #da190b;
|
||||
}
|
||||
.uninstall-btn {
|
||||
background: gray;
|
||||
color: white;
|
||||
}
|
||||
.uninstall-btn:hover {
|
||||
background: dimgray;
|
||||
}
|
||||
.refresh-btn {
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
padding: 10px 20px;
|
||||
background: #31c27c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.refresh-btn:hover {
|
||||
background: #28a869;
|
||||
}
|
||||
/* 顶部按钮的样式 */
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
flex-wrap: wrap;
|
||||
width: 100%; /* 确保容器占满可用宽度 */
|
||||
}
|
||||
.header-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
color: #31c27c;
|
||||
border: 1px solid #31c27c;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.header-btn:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.material-icons {
|
||||
font-size: 20px;
|
||||
}
|
||||
.edit-btn {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-btn:hover {
|
||||
background: #1976d2;
|
||||
}
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.version-info {
|
||||
position: absolute;
|
||||
@@ -48,167 +242,6 @@
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #d32f2f;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
/* 插件设置专用样式 */
|
||||
.plugins-section {
|
||||
padding: 20px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.plugin-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
.plugin-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
}
|
||||
.plugin-info {
|
||||
flex: 1;
|
||||
}
|
||||
.plugin-name {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.plugin-status {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 5px; /* 添加上边距 */
|
||||
}
|
||||
.status-enabled {
|
||||
background: #e8f5e9;
|
||||
color: #4caf50;
|
||||
}
|
||||
.status-disabled {
|
||||
background: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
.plugin-details {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.plugin-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.action-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.enable-btn {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
.enable-btn:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
.disable-btn {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
.disable-btn:hover {
|
||||
background: #da190b;
|
||||
}
|
||||
.uninstall-btn {
|
||||
background: gray;
|
||||
color: white;
|
||||
}
|
||||
.uninstall-btn:hover {
|
||||
background: dimgray;
|
||||
}
|
||||
.refresh-btn {
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
padding: 10px 20px;
|
||||
background: #31c27c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.refresh-btn:hover {
|
||||
background: #28a869;
|
||||
}
|
||||
/* 顶部按钮的样式 */
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
flex-wrap: wrap;
|
||||
width: 100%; /* 确保容器占满可用宽度 */
|
||||
}
|
||||
.header-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
color: #31c27c;
|
||||
border: 1px solid #31c27c;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.header-btn:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.material-icons {
|
||||
font-size: 20px;
|
||||
}
|
||||
.edit-btn {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-btn:hover {
|
||||
background: #1976d2;
|
||||
}
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 15px 10px;
|
||||
@@ -254,30 +287,52 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏设备进一步优化 */
|
||||
@media (max-width: 480px) {
|
||||
.header-buttons {
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
flex: 0 0 calc(50% - 3px); /* 每行2个按钮 */
|
||||
min-width: 90px;
|
||||
max-width: 120px;
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.plugin-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* 防止动画影响性能 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
.plugin-info {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.plugin-actions {
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏设备进一步优化 */
|
||||
@media (max-width: 480px) {
|
||||
.header-buttons {
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
flex: 0 0 calc(50% - 3px); /* 每行2个按钮 */
|
||||
min-width: 90px;
|
||||
max-width: 120px;
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 防止动画影响性能 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -334,8 +389,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 插件配置 内容 -->
|
||||
<div class="section-title">插件配置</div>
|
||||
<!-- 插件源配置 -->
|
||||
<div class="section-title">插件源配置</div>
|
||||
<div id="plugin-source">
|
||||
<div class="plugin-item">
|
||||
<div class="plugin-info">
|
||||
<div class="plugin-details">
|
||||
<div><strong>插件源地址: </strong> <span id="source-url"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plugin-actions">
|
||||
<button class="action-btn enable-btn" id="refresh-source-btn" onclick="refreshPluginSource()">更新订阅</button>
|
||||
<!-- 新增编辑按钮 -->
|
||||
<button class="action-btn edit-btn" id="edit-source-btn" onclick="editPluginSource()" style="display: inline-block;">编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-bottom"></div>
|
||||
<div id="plugins-container">
|
||||
<div class="loading">加载中...</div>
|
||||
</div>
|
||||
@@ -355,10 +425,117 @@
|
||||
versionSpan.textContent = `v${window.appConfig.version}`;
|
||||
}
|
||||
}
|
||||
//加载插件
|
||||
//加载数据
|
||||
loadPlugins();
|
||||
loadOpenApiConfig();
|
||||
loadPluginSource();
|
||||
});
|
||||
|
||||
/*============================插件源相关函数=================================*/
|
||||
// 加载 OpenAPI 配置
|
||||
async function loadPluginSource() {
|
||||
const container = document.getElementById('plugin-source');
|
||||
try {
|
||||
const response = await fetch('/api/plugin-source/load');
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
displayPluginSource(data.data);
|
||||
} else {
|
||||
container.innerHTML = `<div class="error">加载失败: ${data.error}</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = `<div class="error">加载出错: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示 OpenAPI 配置
|
||||
function displayPluginSource(config) {
|
||||
document.getElementById('source-url').textContent = config.source_url || '';
|
||||
const editButtonElement = document.getElementById('edit-source-btn');
|
||||
if (config.source_url && config.source_url.length > 0) {
|
||||
editButtonElement.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 刷新订阅源
|
||||
async function refreshPluginSource() {
|
||||
try {
|
||||
const urlElement = document.getElementById('source-url');
|
||||
const currentUrl = urlElement.textContent;
|
||||
if (!currentUrl) {
|
||||
alert('请先设置接口地址!');
|
||||
return;
|
||||
}
|
||||
if (!confirm(`确定要刷新订阅源吗?相同名称插件将被覆盖!`)) {
|
||||
return;
|
||||
}
|
||||
const response = await fetch('/api/plugin-source/refresh', {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// 操作成功,重新加载插件列表
|
||||
await loadPlugins();
|
||||
} else {
|
||||
alert(`切换失败: ${data.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`操作出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑插件订阅源地址
|
||||
function editPluginSource() {
|
||||
const urlElement = document.getElementById('source-url');
|
||||
const currentUrl = urlElement.textContent;
|
||||
const newUrl = prompt('请输入新的插件源地址:', currentUrl);
|
||||
|
||||
// 检查用户是否点击了取消
|
||||
if (newUrl === null) {
|
||||
// 用户点击了取消,不执行任何操作
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户是否输入了空字符串
|
||||
if (newUrl.trim() === '') {
|
||||
alert('插件源地址不能为空!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验URL格式
|
||||
const urlPattern = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
|
||||
if (urlPattern.test(newUrl)) {
|
||||
// 更新地址
|
||||
updatePluginSource(newUrl);
|
||||
} else {
|
||||
alert('请输入有效的插件源地址!');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新OpenAPI地址
|
||||
async function updatePluginSource(newUrl) {
|
||||
try {
|
||||
const response = await fetch('/api/plugin-source/updateUrl', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ source_url: newUrl })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// 更新成功,重新加载插件列表
|
||||
await loadPluginSource();
|
||||
} else {
|
||||
alert(`更新失败: ${data.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`更新出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
/*============================开放接口函数=================================*/
|
||||
// 加载 OpenAPI 配置
|
||||
async function loadOpenApiConfig() {
|
||||
@@ -432,16 +609,30 @@
|
||||
const urlElement = document.getElementById('openapi-url');
|
||||
const currentUrl = urlElement.textContent;
|
||||
const newUrl = prompt('请输入新的接口地址:', currentUrl);
|
||||
// 校验newUrl格式
|
||||
|
||||
// 检查用户是否点击了取消
|
||||
if (newUrl === null) {
|
||||
// 用户点击了取消,不执行任何操作
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户是否输入了空字符串
|
||||
if (newUrl.trim() === '') {
|
||||
alert('接口地址不能为空!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验URL格式
|
||||
const urlPattern = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
|
||||
if (newUrl && urlPattern.test(newUrl)) {
|
||||
if (urlPattern.test(newUrl)) {
|
||||
// 更新接口地址
|
||||
updateOpenApiUrl(newUrl);
|
||||
}else {
|
||||
} else {
|
||||
alert('请输入有效的接口地址!');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 更新OpenAPI地址
|
||||
async function updateOpenApiUrl(newUrl) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user