From 49c2855b103e8d9c7cc935301b9377240bdf40c2 Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 4 Nov 2025 11:30:14 +0800 Subject: [PATCH] feat(usage): add support for access token and user ID in usage scripts Add optional accessToken and userId fields to usage query scripts, enabling queries to authenticated endpoints like /api/user/self. Changes: - Add accessToken and userId fields to UsageScript type (frontend & backend) - Extend script engine to support {{accessToken}} and {{userId}} placeholders - Update NewAPI preset template to use /api/user/self endpoint - Add UI inputs for access token and user ID in UsageScriptModal - Pass new parameters through service layer to script executor This allows users to query usage data from endpoints that require login credentials, providing more accurate balance information for services like NewAPI/OneAPI platforms. --- src-tauri/src/provider.rs | 8 ++++ src-tauri/src/services/provider.rs | 19 ++++++-- src-tauri/src/usage_script.rs | 12 ++++- src/components/UsageScriptModal.tsx | 72 ++++++++++++++++++++--------- src/types.ts | 2 + 5 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src-tauri/src/provider.rs b/src-tauri/src/provider.rs index 08e9cb2..5a2c0ad 100644 --- a/src-tauri/src/provider.rs +++ b/src-tauri/src/provider.rs @@ -63,6 +63,14 @@ pub struct UsageScript { pub code: String, #[serde(skip_serializing_if = "Option::is_none")] pub timeout: Option, + /// 访问令牌(用于需要登录的接口) + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "accessToken")] + pub access_token: Option, + /// 用户ID(用于需要用户标识的接口) + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "userId")] + pub user_id: Option, } /// 用量数据 diff --git a/src-tauri/src/services/provider.rs b/src-tauri/src/services/provider.rs index 625f9f4..492da7b 100644 --- a/src-tauri/src/services/provider.rs +++ b/src-tauri/src/services/provider.rs @@ -717,7 +717,7 @@ impl ProviderService { app_type: AppType, provider_id: &str, ) -> Result { - let (provider, script_code, timeout) = { + let (provider, script_code, timeout, access_token, user_id) = { let config = state.config.read().map_err(AppError::from)?; let manager = config .get_manager(&app_type) @@ -727,7 +727,7 @@ impl ProviderService { .get(provider_id) .cloned() .ok_or_else(|| AppError::ProviderNotFound(provider_id.to_string()))?; - let (script_code, timeout) = { + let (script_code, timeout, access_token, user_id) = { let usage_script = provider .meta .as_ref() @@ -749,15 +749,26 @@ impl ProviderService { ( usage_script.code.clone(), usage_script.timeout.unwrap_or(10), + usage_script.access_token.clone(), + usage_script.user_id.clone(), ) }; - (provider, script_code, timeout) + (provider, script_code, timeout, access_token, user_id) }; let (api_key, base_url) = Self::extract_credentials(&provider, &app_type)?; - match usage_script::execute_usage_script(&script_code, &api_key, &base_url, timeout).await { + match usage_script::execute_usage_script( + &script_code, + &api_key, + &base_url, + timeout, + access_token.as_deref(), + user_id.as_deref(), + ) + .await + { Ok(data) => { let usage_list: Vec = if data.is_array() { serde_json::from_value(data) diff --git a/src-tauri/src/usage_script.rs b/src-tauri/src/usage_script.rs index d88633c..07a9dde 100644 --- a/src-tauri/src/usage_script.rs +++ b/src-tauri/src/usage_script.rs @@ -12,12 +12,22 @@ pub async fn execute_usage_script( api_key: &str, base_url: &str, timeout_secs: u64, + access_token: Option<&str>, + user_id: Option<&str>, ) -> Result { // 1. 替换变量 - let replaced = script_code + let mut replaced = script_code .replace("{{apiKey}}", api_key) .replace("{{baseUrl}}", base_url); + // 替换 accessToken 和 userId + if let Some(token) = access_token { + replaced = replaced.replace("{{accessToken}}", token); + } + if let Some(uid) = user_id { + replaced = replaced.replace("{{userId}}", uid); + } + // 2. 在独立作用域中提取 request 配置(确保 Runtime/Context 在 await 前释放) let request_config = { let runtime = diff --git a/src/components/UsageScriptModal.tsx b/src/components/UsageScriptModal.tsx index 1f84283..fda291b 100644 --- a/src/components/UsageScriptModal.tsx +++ b/src/components/UsageScriptModal.tsx @@ -47,37 +47,28 @@ const PRESET_TEMPLATES: Record = { NewAPI: `({ request: { - url: "{{baseUrl}}/api/usage/token", + url: "{{baseUrl}}/api/user/self", method: "GET", headers: { - Authorization: "Bearer {{apiKey}}", + "Content-Type": "application/json", + "Authorization": "Bearer {{accessToken}}", + "New-Api-User": "{{userId}}" }, }, extractor: function (response) { - if (response.code) { - if (response.data.unlimited_quota) { - return { - planName: response.data.name, - total: -1, - used: response.data.total_used / 500000, - unit: "USD", - }; - } + if (response.success && response.data) { return { - isValid: true, - planName: response.data.name, - total: response.data.total_granted / 500000, - used: response.data.total_used / 500000, - remaining: response.data.total_available / 500000, + planName: response.data.group || "默认套餐", + remaining: response.data.quota / 500000, + used: response.data.used_quota / 500000, + total: (response.data.quota + response.data.used_quota) / 500000, unit: "USD", }; } - if (response.error) { - return { - isValid: false, - invalidMessage: response.error.message, - }; - } + return { + isValid: false, + invalidMessage: response.message || "查询失败" + }; }, })`, }; @@ -275,6 +266,43 @@ const UsageScriptModal: React.FC = ({ + {/* 高级配置:Access Token 和 User ID */} +
+

+ 🔑 高级配置(可选,用于需要登录的接口) +

+ + + + + +

+ 💡 在脚本中使用:{`{{accessToken}}`} 和 {`{{userId}}`} +

+
+ {/* 脚本说明 */}

diff --git a/src/types.ts b/src/types.ts index f6d020c..81d9369 100644 --- a/src/types.ts +++ b/src/types.ts @@ -43,6 +43,8 @@ export interface UsageScript { language: "javascript"; // 脚本语言 code: string; // 脚本代码(JSON 格式配置) timeout?: number; // 超时时间(秒,默认 10) + accessToken?: string; // 访问令牌(用于需要登录的接口) + userId?: string; // 用户ID(用于需要用户标识的接口) } // 单个套餐用量数据