-- ============================================ -- Cloudreve V4 工具箱 - Lua GG Script -- 作者: Claude AI -- 版本: 1.0 -- ============================================ -- ==================== 配置 ==================== local CONFIG = { BASE_URL = "http://46.224.69.39:47507", -- 修改为你的Cloudreve地址 API_PREFIX = "/api/v4", MAX_APPS_PER_USER = 5, -- 每个用户最多创建的应用数 APP_DATA_DIR = "/CloudToolbox/", -- 在网盘中的存储目录 VERSION = "1.0.0", SCRIPT_UPDATE_URL = "https://your-domain.com/toolbox_version.json", -- 脚本云更新地址 } -- ==================== 全局变量 ==================== local TOKEN = nil local USER_INFO = nil local CURRENT_APPS = {} -- ==================== HTTP 请求封装 ==================== local function httpRequest(method, endpoint, body, headers) local url = CONFIG.BASE_URL .. CONFIG.API_PREFIX .. endpoint local defaultHeaders = { ["Content-Type"] = "application/json", ["Accept"] = "application/json", } if TOKEN then defaultHeaders["Authorization"] = "Bearer " .. TOKEN end if headers then for k, v in pairs(headers) do defaultHeaders[k] = v end end local requestBody = "" if body then requestBody = jsonEncode(body) end local response = nil local err = nil if method == "GET" then response = gg.makeRequest(url, defaultHeaders) elseif method == "POST" then response = gg.makeRequest(url, defaultHeaders, requestBody) elseif method == "PUT" then response = gg.makeRequest(url, defaultHeaders, requestBody, "PUT") elseif method == "DELETE" then response = gg.makeRequest(url, defaultHeaders, nil, "DELETE") elseif method == "PATCH" then response = gg.makeRequest(url, defaultHeaders, requestBody, "PATCH") end if response and response.content then return jsonDecode(response.content), response.code end return nil, 0 end -- ==================== JSON 编解码 ==================== function jsonEncode(val) if val == nil then return "null" end local vtype = type(val) if vtype == "string" then val = string.gsub(val, '[\\"]', '\\%1') val = string.gsub(val, '\n', '\\n') val = string.gsub(val, '\r', '\\r') val = string.gsub(val, '\t', '\\t') return '"' .. val .. '"' elseif vtype == "number" then return tostring(val) elseif vtype == "boolean" then return val and "true" or "false" elseif vtype == "table" then -- 判断是数组还是对象 local isArray = true local maxIndex = 0 for k, v in pairs(val) do if type(k) ~= "number" then isArray = false break end if k > maxIndex then maxIndex = k end end if isArray and maxIndex == #val then local items = {} for i = 1, #val do items[i] = jsonEncode(val[i]) end return "[" .. table.concat(items, ",") .. "]" else local items = {} for k, v in pairs(val) do table.insert(items, jsonEncode(tostring(k)) .. ":" .. jsonEncode(v)) end return "{" .. table.concat(items, ",") .. "}" end end return "null" end function jsonDecode(str) if str == nil or str == "" then return nil end str = str:match("^%s*(.-)%s*$") if str == "null" then return nil end if str == "true" then return true end if str == "false" then return false end local num = tonumber(str) if num then return num end if str:sub(1,1) == '"' then local s = str:sub(2, -2) s = s:gsub('\\n', '\n') s = s:gsub('\\r', '\r') s = s:gsub('\\t', '\t') s = s:gsub('\\"', '"') s = s:gsub('\\\\', '\\') return s end -- 使用 load 解析复杂JSON local f = load("return " .. str:gsub('%[', '{'):gsub('%]', '}'):gsub('"(%w+)"%s*:', '["%1"]=')) if f then local ok, result = pcall(f) if ok then return result end end -- 备用:简单解析 local success, result = pcall(function() return load("return " .. str)() end) if success then return result end return nil end -- ==================== 本地存储 ==================== local function saveLocalData(key, value) local path = gg.EXT_STORAGE .. "/CloudToolbox/" os.execute("mkdir -p " .. path) local file = io.open(path .. key .. ".dat", "w") if file then file:write(jsonEncode(value)) file:close() return true end return false end local function loadLocalData(key) local path = gg.EXT_STORAGE .. "/CloudToolbox/" .. key .. ".dat" local file = io.open(path, "r") if file then local content = file:read("*all") file:close() return jsonDecode(content) end return nil end local function deleteLocalData(key) local path = gg.EXT_STORAGE .. "/CloudToolbox/" .. key .. ".dat" os.remove(path) end -- ==================== Cloudreve API 接口封装 ==================== -- 用户注册 local function apiRegister(username, password, email) local body = { userName = username, password = password, email = email, } return httpRequest("POST", "/user", body) end -- 用户登录 local function apiLogin(username, password) local body = { userName = username, password = password, } return httpRequest("POST", "/session", body) end -- 获取用户信息 local function apiGetUserInfo() return httpRequest("GET", "/user/me") end -- 创建目录 local function apiCreateDirectory(path) local body = { path = path, } return httpRequest("PUT", "/directory", body) end -- 获取目录列表 local function apiListDirectory(path) return httpRequest("GET", "/directory" .. path) end -- 上传文件 (获取上传凭证) local function apiGetUploadCredential(path, filename, filesize) local body = { path = path, name = filename, size = filesize, policy_id = "", -- 使用默认存储策略 } return httpRequest("POST", "/file/upload", body) end -- 删除文件/目录 local function apiDelete(items) local body = { items = items, -- {dirs = {}, items = {}} } return httpRequest("DELETE", "/object", body) end -- 获取文件直链 local function apiGetDownloadURL(fileId) return httpRequest("GET", "/file/download/" .. fileId) end -- 获取文件信息 local function apiGetFileInfo(path) return httpRequest("GET", "/object/property/" .. path) end -- 上传文件(完整流程) local function uploadFile(localPath, remotePath, filename) -- 读取文件 local file = io.open(localPath, "rb") if not file then return nil, "无法打开本地文件" end local content = file:read("*all") local filesize = #content file:close() -- 获取上传凭证 local credential, code = apiGetUploadCredential(remotePath, filename, filesize) if not credential or code ~= 200 then return nil, "获取上传凭证失败" end -- 执行上传 local uploadUrl = credential.data.upload_url or credential.data.url if uploadUrl then local headers = { ["Content-Type"] = "application/octet-stream", ["Authorization"] = "Bearer " .. TOKEN, ["Content-Length"] = tostring(filesize), } local resp = gg.makeRequest(uploadUrl, headers, content, "POST") if resp and resp.code == 200 then return jsonDecode(resp.content), 200 end end return nil, "上传失败" end -- ==================== 应用管理 ==================== local function getAppsPath() return CONFIG.APP_DATA_DIR end local function getAppPath(appName) return CONFIG.APP_DATA_DIR .. appName .. "/" end -- 加载用户的应用列表 local function loadApps() local apps = loadLocalData("apps") if apps then CURRENT_APPS = apps else CURRENT_APPS = {} end return CURRENT_APPS end -- 保存应用列表 local function saveApps() saveLocalData("apps", CURRENT_APPS) end -- 创建应用 local function createApp(appName, appDesc) loadApps() if #CURRENT_APPS >= CONFIG.MAX_APPS_PER_USER then return false, "已达到最大应用数量限制(" .. CONFIG.MAX_APPS_PER_USER .. "个)" end -- 检查重名 for _, app in ipairs(CURRENT_APPS) do if app.name == appName then return false, "应用名称已存在" end end -- 在Cloudreve创建目录 local appPath = getAppPath(appName) local result, code = apiCreateDirectory(appPath) local newApp = { name = appName, description = appDesc or "", created_at = os.date("%Y-%m-%d %H:%M:%S"), updated_at = os.date("%Y-%m-%d %H:%M:%S"), file_id = nil, file_name = nil, download_url = nil, version = "1.0.0", } table.insert(CURRENT_APPS, newApp) saveApps() return true, "应用创建成功" end -- 删除应用 local function deleteApp(appIndex) loadApps() if appIndex < 1 or appIndex > #CURRENT_APPS then return false, "无效的应用索引" end local app = CURRENT_APPS[appIndex] -- 删除Cloudreve上的目录 local appPath = getAppPath(app.name) apiDelete({dirs = {appPath}, items = {}}) table.remove(CURRENT_APPS, appIndex) saveApps() return true, "应用已删除" end -- 为应用上传文件 local function uploadAppFile(appIndex, localFilePath) loadApps() if appIndex < 1 or appIndex > #CURRENT_APPS then return false, "无效的应用索引" end local app = CURRENT_APPS[appIndex] local appPath = getAppPath(app.name) -- 提取文件名 local filename = localFilePath:match("([^/]+)$") if not filename then return false, "无法获取文件名" end -- 如果已有文件,先删除旧文件 if app.file_id then apiDelete({dirs = {}, items = {app.file_id}}) end -- 上传新文件 local result, err = uploadFile(localFilePath, appPath, filename) if not result then return false, "上传失败: " .. tostring(err) end -- 获取直链 local fileId = result.data and result.data.id or nil local downloadUrl = nil if fileId then local dlResult, dlCode = apiGetDownloadURL(fileId) if dlResult and dlResult.data then downloadUrl = dlResult.data end end -- 更新应用信息 app.file_id = fileId app.file_name = filename app.download_url = downloadUrl app.updated_at = os.date("%Y-%m-%d %H:%M:%S") -- 自增版本号 local major, minor, patch = app.version:match("(%d+)%.(%d+)%.(%d+)") if patch then app.version = major .. "." .. minor .. "." .. tostring(tonumber(patch) + 1) end CURRENT_APPS[appIndex] = app saveApps() return true, "文件上传成功\n直链: " .. tostring(downloadUrl) end -- 替换应用文件(实现云更新) local function replaceAppFile(appIndex, localFilePath) return uploadAppFile(appIndex, localFilePath) -- 复用上传逻辑,会自动删除旧文件 end -- 获取应用直链 local function getAppDirectLink(appIndex) loadApps() if appIndex < 1 or appIndex > #CURRENT_APPS then return nil, "无效的应用索引" end local app = CURRENT_APPS[appIndex] if app.file_id then -- 重新获取最新直链 local result, code = apiGetDownloadURL(app.file_id) if result and result.data then app.download_url = result.data CURRENT_APPS[appIndex] = app saveApps() return app.download_url end end return app.download_url, "无文件或获取直链失败" end -- ==================== 界面系统 ==================== -- 显示提示 local function showToast(msg) gg.toast(tostring(msg)) end -- 显示消息框 local function showAlert(title, msg) gg.alert(tostring(msg), nil, nil, title) end -- ============ 注册界面 ============ local function showRegisterUI() local inputs = gg.prompt( {"👤 用户名", "📧 邮箱", "🔑 密码", "🔑 确认密码"}, {nil, nil, nil, nil}, {"text", "text", "text", "text"} ) if not inputs then return end local username = inputs[1] local email = inputs[2] local password = inputs[3] local confirmPwd = inputs[4] if not username or username == "" then showAlert("错误", "请输入用户名") return end if not email or email == "" then showAlert("错误", "请输入邮箱") return end if not password or password == "" then showAlert("错误", "请输入密码") return end if password ~= confirmPwd then showAlert("错误", "两次密码输入不一致") return end showToast("正在注册...") local result, code = apiRegister(username, password, email) if result and (code == 200 or code == 201) then showAlert("成功", "注册成功!请登录") else local errMsg = "注册失败" if result and result.msg then errMsg = errMsg .. ": " .. result.msg elseif result and result.error then errMsg = errMsg .. ": " .. result.error end showAlert("错误", errMsg) end end -- ============ 登录界面 ============ local function showLoginUI() -- 尝试自动登录 local savedAuth = loadLocalData("auth") if savedAuth and savedAuth.token then TOKEN = savedAuth.token local userInfo, code = apiGetUserInfo() if userInfo and code == 200 then USER_INFO = userInfo.data or userInfo showToast("自动登录成功!欢迎回来 " .. (USER_INFO.nickname or USER_INFO.user_name or "")) return true else TOKEN = nil end end local inputs = gg.prompt( {"👤 用户名", "🔑 密码", "💾 记住登录"}, {nil, nil, true}, {"text", "text", "checkbox"} ) if not inputs then return false end local username = inputs[1] local password = inputs[2] local remember = inputs[3] if not username or username == "" or not password or password == "" then showAlert("错误", "请输入用户名和密码") return false end showToast("正在登录...") local result, code = apiLogin(username, password) if result and code == 200 then TOKEN = result.data and result.data.token or result.token if not TOKEN then showAlert("错误", "登录返回异常,未获取到Token") return false end -- 获取用户信息 local userInfo = apiGetUserInfo() if userInfo then USER_INFO = userInfo.data or userInfo end -- 记住登录 if remember then saveLocalData("auth", { token = TOKEN, username = username, }) end -- 初始化应用目录 apiCreateDirectory(CONFIG.APP_DATA_DIR) showAlert("成功", "登录成功!欢迎 " .. (USER_INFO and (USER_INFO.nickname or USER_INFO.user_name) or username)) return true else local errMsg = "登录失败" if result and result.msg then errMsg = errMsg .. ": " .. result.msg end showAlert("错误", errMsg) return false end end -- ============ 应用列表界面 ============ local function showAppListUI() loadApps() local menuItems = {} for i, app in ipairs(CURRENT_APPS) do local status = app.file_name and "✅" or "⬜" table.insert(menuItems, status .. " " .. app.name .. " [v" .. app.version .. "]") end table.insert(menuItems, "➕ 创建新应用") table.insert(menuItems, "🔙 返回主菜单") local choice = gg.choice(menuItems, nil, "📱 我的应用 (" .. #CURRENT_APPS .. "/" .. CONFIG.MAX_APPS_PER_USER .. ")") if not choice then return end if choice == #menuItems then -- 返回 return elseif choice == #menuItems - 1 then -- 创建新应用 showCreateAppUI() else -- 选择了某个应用 showAppDetailUI(choice) end -- 循环显示 showAppListUI() end -- ============ 创建应用界面 ============ local function showCreateAppUI() loadApps() if #CURRENT_APPS >= CONFIG.MAX_APPS_PER_USER then showAlert("限制", "已达到最大应用数量限制(" .. CONFIG.MAX_APPS_PER_USER .. "个)\n请删除不需要的应用后再创建") return end local inputs = gg.prompt( {"📱 应用名称", "📝 应用描述"}, {nil, nil}, {"text", "text"} ) if not inputs then return end local appName = inputs[1] local appDesc = inputs[2] if not appName or appName == "" then showAlert("错误", "请输入应用名称") return end -- 过滤特殊字符 appName = appName:gsub("[^%w_%-]", "_") showToast("正在创建应用...") local success, msg = createApp(appName, appDesc) showAlert(success and "成功" or "错误", msg) end -- ============ 应用详情界面 ============ function showAppDetailUI(appIndex) loadApps() if appIndex < 1 or appIndex > #CURRENT_APPS then return end local app = CURRENT_APPS[appIndex] local info = "📱 应用: " .. app.name .. "\n" info = info .. "📝 描述: " .. (app.description or "无") .. "\n" info = info .. "📌 版本: v" .. app.version .. "\n" info = info .. "📅 创建: " .. app.created_at .. "\n" info = info .. "🔄 更新: " .. app.updated_at .. "\n" info = info .. "📄 文件: " .. (app.file_name or "未上传") .. "\n" info = info .. "🔗 直链: " .. (app.download_url or "无") .. "\n" local menuItems = { "📤 上传文件", "🔄 替换文件 (云更新)", "🔗 获取/刷新直链", "📋 复制直链", "🗑️ 删除应用", "🔙 返回应用列表", } local choice = gg.choice(menuItems, nil, info) if not choice then return end if choice == 1 then -- 上传文件 showUploadFileUI(appIndex) elseif choice == 2 then -- 替换文件 showReplaceFileUI(appIndex) elseif choice == 3 then -- 获取直链 showToast("正在获取直链...") local url, err = getAppDirectLink(appIndex) if url then showAlert("直链", "🔗 下载直链:\n\n" .. url) else showAlert("提示", err or "无法获取直链,请先上传文件") end elseif choice == 4 then -- 复制直链 if app.download_url then gg.copyText(app.download_url) showToast("直链已复制到剪贴板") else showToast("无直链可复制") end elseif choice == 5 then -- 删除应用 local confirm = gg.choice({"✅ 确认删除", "❌ 取消"}, nil, "⚠️ 确定要删除应用 [" .. app.name .. "] 吗?\n此操作不可恢复!") if confirm == 1 then showToast("正在删除...") local success, msg = deleteApp(appIndex) showAlert(success and "成功" or "错误", msg) return -- 返回应用列表 end elseif choice == 6 then return end -- 循环显示 showAppDetailUI(appIndex) end -- ============ 上传文件界面 ============ local function showUploadFileUI(appIndex) local inputs = gg.prompt( {"📂 文件完整路径\n(例: /sdcard/Download/app.apk)"}, {"/sdcard/Download/"}, {"file"} ) if not inputs then return end local filePath = inputs[1] if not filePath or filePath == "" then showAlert("错误", "请输入文件路径") return end -- 检查文件是否存在 local file = io.open(filePath, "rb") if not file then showAlert("错误", "文件不存在: " .. filePath) return end file:close() showToast("正在上传文件...") local success, msg = uploadAppFile(appIndex, filePath) showAlert(success and "成功" or "错误", msg) end -- ============ 替换文件界面 ============ function showReplaceFileUI(appIndex) loadApps() local app = CURRENT_APPS[appIndex] if not app.file_name then showAlert("提示", "当前应用还没有上传过文件,请先上传") showUploadFileUI(appIndex) return end local currentInfo = "当前文件: " .. app.file_name .. "\n当前版本: v" .. app.version local inputs = gg.prompt( {"📂 新文件路径", "📌 新版本号 (留空自动递增)"}, {"/sdcard/Download/" .. app.file_name, nil}, {"file", "text"} ) if not inputs then return end local filePath = inputs[1] local newVersion = inputs[2] if not filePath or filePath == "" then showAlert("错误", "请输入文件路径") return end local file = io.open(filePath, "rb") if not file then showAlert("错误", "文件不存在: " .. filePath) return end file:close() showToast("正在替换文件(云更新)...") local success, msg = replaceAppFile(appIndex, filePath) -- 如果指定了版本号 if success and newVersion and newVersion ~= "" then loadApps() CURRENT_APPS[appIndex].version = newVersion saveApps() end showAlert(success and "✅ 云更新成功" or "❌ 更新失败", msg) end -- ============ 云更新检查(脚本自身) ============ local function checkScriptUpdate() showToast("正在检查更新...") local resp = gg.makeRequest(CONFIG.SCRIPT_UPDATE_URL) if resp and resp.content then local updateInfo = jsonDecode(resp.content) if updateInfo and updateInfo.version then if updateInfo.version ~= CONFIG.VERSION then local choice = gg.choice( {"✅ 立即更新", "❌ 暂不更新"}, nil, "🔄 发现新版本!\n\n" .. "当前版本: v" .. CONFIG.VERSION .. "\n" .. "最新版本: v" .. updateInfo.version .. "\n\n" .. "更新内容:\n" .. (updateInfo.changelog or "无") ) if choice == 1 and updateInfo.download_url then showToast("正在下载更新...") local dlResp = gg.makeRequest(updateInfo.download_url) if dlResp and dlResp.content then -- 保存新脚本 local scriptPath = gg.EXT_STORAGE .. "/CloudToolbox/CloudreveToolbox.lua" local file = io.open(scriptPath, "w") if file then file:write(dlResp.content) file:close() showAlert("更新成功", "脚本已更新到 v" .. updateInfo.version .. "\n请重新运行脚本") os.exit() end else showAlert("错误", "下载更新失败") end end else showAlert("提示", "当前已是最新版本 v" .. CONFIG.VERSION) end end else showAlert("错误", "检查更新失败,请检查网络连接") end end -- ============ 设置界面 ============ local function showSettingsUI() local menuItems = { "🌐 修改服务器地址", "📊 修改最大应用数量", "🗑️ 清除登录信息", "🗑️ 清除所有本地数据", "🔙 返回", } local choice = gg.choice(menuItems, nil, "⚙️ 设置") if not choice then return end if choice == 1 then local inputs = gg.prompt( {"🌐 服务器地址"}, {CONFIG.BASE_URL}, {"text"} ) if inputs and inputs[1] then CONFIG.BASE_URL = inputs[1]:gsub("/$", "") -- 去掉末尾斜杠 saveLocalData("config", CONFIG) showToast("服务器地址已更新") end elseif choice == 2 then local inputs = gg.prompt( {"📊 最大应用数量"}, {tostring(CONFIG.MAX_APPS_PER_USER)}, {"number"} ) if inputs and inputs[1] then CONFIG.MAX_APPS_PER_USER = tonumber(inputs[1]) or 5 saveLocalData("config", CONFIG) showToast("最大应用数量已更新为 " .. CONFIG.MAX_APPS_PER_USER) end elseif choice == 3 then deleteLocalData("auth") TOKEN = nil USER_INFO = nil showToast("登录信息已清除") elseif choice == 4 then local confirm = gg.choice({"✅ 确认清除", "❌ 取消"}, nil, "⚠️ 确定要清除所有本地数据吗?") if confirm == 1 then deleteLocalData("auth") deleteLocalData("apps") deleteLocalData("config") TOKEN = nil USER_INFO = nil CURRENT_APPS = {} showToast("所有本地数据已清除") end end end -- ============ 用户信息界面 ============ local function showUserInfoUI() if not USER_INFO then showAlert("提示", "未获取到用户信息") return end local info = "👤 用户信息\n\n" info = info .. "用户名: " .. (USER_INFO.user_name or USER_INFO.userName or "未知") .. "\n" info = info .. "昵称: " .. (USER_INFO.nickname or USER_INFO.nick or "未设置") .. "\n" info = info .. "邮箱: " .. (USER_INFO.email or "未设置") .. "\n" info = info .. "用户组: " .. (USER_INFO.group and USER_INFO.group.name or "未知") .. "\n" info = info .. "存储空间: " .. (USER_INFO.storage and ( string.format("%.2f", (USER_INFO.storage.used or 0) / 1024 / 1024) .. " MB / " .. string.format("%.2f", (USER_INFO.storage.total or 0) / 1024 / 1024) .. " MB" ) or "未知") .. "\n" showAlert("用户信息", info) end -- ==================== 主菜单 ==================== local function showMainMenu() local username = "" if USER_INFO then username = USER_INFO.nickname or USER_INFO.user_name or USER_INFO.userName or "" end loadApps() local menuItems = { "📱 我的应用 (" .. #CURRENT_APPS .. ")", "➕ 快速创建应用", "👤 用户信息", "🔄 检查脚本更新", "⚙️ 设置", "🚪 退出登录", "❌ 退出工具箱", } local title = "☁️ Cloudreve 云更新工具箱 v" .. CONFIG.VERSION if username ~= "" then title = title .. "\n👋 欢迎, " .. username end local choice = gg.choice(menuItems, nil, title) if not choice then return true -- 继续运行 end if choice == 1 then showAppListUI() elseif choice == 2 then showCreateAppUI() elseif choice == 3 then showUserInfoUI() elseif choice == 4 then checkScriptUpdate() elseif choice == 5 then showSettingsUI() elseif choice == 6 then TOKEN = nil USER_INFO = nil deleteLocalData("auth") showToast("已退出登录") return false -- 回到登录界面 elseif choice == 7 then showToast("感谢使用!再见 👋") os.exit() end return true end -- ==================== 启动入口 ==================== local function showWelcomeUI() local menuItems = { "🔑 登录", "📝 注册", "⚙️ 设置服务器地址", "❌ 退出", } local choice = gg.choice(menuItems, nil, "☁️ Cloudreve 云更新工具箱\n" .. "📌 版本: v" .. CONFIG.VERSION .. "\n" .. "🌐 服务器: " .. CONFIG.BASE_URL ) if not choice then return "exit" end if choice == 1 then return "login" elseif choice == 2 then return "register" elseif choice == 3 then return "settings" elseif choice == 4 then return "exit" end end -- ==================== 主程序 ==================== local function main() -- 加载保存的配置 local savedConfig = loadLocalData("config") if savedConfig then for k, v in pairs(savedConfig) do CONFIG[k] = v end end -- 初始化存储目录 os.execute("mkdir -p " .. gg.EXT_STORAGE .. "/CloudToolbox/") showToast("☁️ Cloudreve 工具箱启动中...") -- 尝试自动登录 local savedAuth = loadLocalData("auth") if savedAuth and savedAuth.token then TOKEN = savedAuth.token showToast("正在自动登录...") local userInfo, code = apiGetUserInfo() if userInfo and (code == 200 or code == 0) then USER_INFO = userInfo.data or userInfo showToast("自动登录成功!") -- 直接进入主菜单 while true do local continueLoop = showMainMenu() if not continueLoop then break -- 退出登录,回到欢迎界面 end end else TOKEN = nil end end -- 欢迎/登录循环 while true do local action = showWelcomeUI() if action == "exit" then showToast("再见!👋") os.exit() elseif action == "login" then local success = showLoginUI() if success then -- 进入主菜单循环 while true do local continueLoop = showMainMenu() if not continueLoop then break end end end elseif action == "register" then showRegisterUI() elseif action == "settings" then local inputs = gg.prompt( {"🌐 服务器地址"}, {CONFIG.BASE_URL}, {"text"} ) if inputs and inputs[1] then CONFIG.BASE_URL = inputs[1]:gsub("/$", "") saveLocalData("config", CONFIG) showToast("服务器地址已更新: " .. CONFIG.BASE_URL) end end end end -- ==================== 启动 ==================== main()