mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat: 新增 Anyrouter/Cubence/YesCode 架构及余额监控增强
- 新增三个 provider 架构:Anyrouter、Cubence、YesCode - 前端新增对应的认证配置模板 - 余额监控增强:支持窗口限额显示(进度条+倒计时)、签到状态、错误信息展示 - 架构基类新增 prepare_verify_config 异步预处理方法 - API 返回 ops_architecture_id 字段用于前端展示
This commit is contained in:
84
frontend/src/features/providers/auth-templates/anyrouter.ts
Normal file
84
frontend/src/features/providers/auth-templates/anyrouter.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Anyrouter 认证模板
|
||||
*
|
||||
* 适用于 Anyrouter 中转站:
|
||||
* - 使用 Cookie 认证(session)
|
||||
* - 自动处理 acw_sc__v2 反爬 Cookie(后端处理)
|
||||
* - quota 单位是 1/500000 美元
|
||||
*/
|
||||
|
||||
import type { AuthTemplate, AuthTemplateFieldGroup } from './types'
|
||||
import type { SaveConfigRequest } from '@/api/providerOps'
|
||||
|
||||
export const anyrouterTemplate: AuthTemplate = {
|
||||
id: 'anyrouter',
|
||||
name: 'Anyrouter',
|
||||
description: '适用于 Anyrouter 中转站,使用 Cookie 认证',
|
||||
|
||||
getFields(providerWebsite?: string): AuthTemplateFieldGroup[] {
|
||||
return [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
key: 'base_url',
|
||||
label: '站点地址',
|
||||
type: 'text',
|
||||
placeholder: providerWebsite || 'https://anyrouter.top',
|
||||
required: !providerWebsite,
|
||||
helpText: '通常为 https://anyrouter.top',
|
||||
},
|
||||
{
|
||||
key: 'session_cookie',
|
||||
label: 'Cookie',
|
||||
type: 'password',
|
||||
placeholder: 'session=MTc2ODc4...; acw_sc__v2=...',
|
||||
required: true,
|
||||
sensitive: true,
|
||||
helpText: '从浏览器开发者工具复制完整 Cookie,或仅填写 session 值',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest {
|
||||
const baseUrl = formData.base_url || providerWebsite || ''
|
||||
|
||||
return {
|
||||
architecture_id: 'anyrouter',
|
||||
base_url: baseUrl,
|
||||
connector: {
|
||||
auth_type: 'cookie',
|
||||
config: {},
|
||||
credentials: {
|
||||
session_cookie: formData.session_cookie,
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
schedule: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseConfig(config: any): Record<string, any> {
|
||||
return {
|
||||
base_url: config?.base_url || '',
|
||||
session_cookie: config?.connector?.credentials?.session_cookie || '',
|
||||
}
|
||||
},
|
||||
|
||||
validate(formData: Record<string, any>): string | null {
|
||||
if (!formData.session_cookie?.trim()) {
|
||||
return '请填写 Session Cookie'
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
formatQuota(quota: number): string {
|
||||
// Anyrouter 的 quota 单位是 1/500000 美元
|
||||
const usd = quota / 500000
|
||||
if (usd >= 1) {
|
||||
return `$${usd.toFixed(2)}`
|
||||
}
|
||||
return `$${usd.toFixed(4)}`
|
||||
},
|
||||
}
|
||||
157
frontend/src/features/providers/auth-templates/cubence.ts
Normal file
157
frontend/src/features/providers/auth-templates/cubence.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Cubence 认证模板
|
||||
*
|
||||
* 适用于 Cubence 中转站:
|
||||
* - 使用 Cookie 认证(token JWT)
|
||||
* - 余额单位直接是美元
|
||||
* - 支持窗口限额查询(5小时/每周)
|
||||
*/
|
||||
|
||||
import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types'
|
||||
import type { SaveConfigRequest } from '@/api/providerOps'
|
||||
|
||||
/**
|
||||
* 格式化窗口限额显示(百分比格式)
|
||||
* 单位是美元(原始值除以 1000000)
|
||||
*/
|
||||
function formatWindowLimit(limit: {
|
||||
limit?: number
|
||||
used?: number
|
||||
remaining?: number
|
||||
resets_at?: number
|
||||
}): { text: string; percent: number } {
|
||||
if (!limit || limit.remaining === undefined || limit.limit === undefined || limit.limit === 0) {
|
||||
return { text: '-', percent: 0 }
|
||||
}
|
||||
|
||||
// 计算剩余百分比
|
||||
const percent = Math.round((limit.remaining / limit.limit) * 100)
|
||||
|
||||
return { text: `${percent}%`, percent }
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化窗口限额详细信息(用于 tooltip)
|
||||
*/
|
||||
function formatWindowLimitDetail(limit: {
|
||||
limit?: number
|
||||
used?: number
|
||||
remaining?: number
|
||||
resets_at?: number
|
||||
}): string {
|
||||
if (!limit || limit.remaining === undefined || limit.limit === undefined) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 转换为美元(原始值除以 1000000)
|
||||
const remaining = (limit.remaining / 1000000).toFixed(2)
|
||||
const total = (limit.limit / 1000000).toFixed(2)
|
||||
|
||||
return `$${remaining} / $${total}`
|
||||
}
|
||||
|
||||
export const cubenceTemplate: AuthTemplate = {
|
||||
id: 'cubence',
|
||||
name: 'Cubence',
|
||||
description: '适用于 Cubence 中转站,使用 Cookie 认证',
|
||||
|
||||
getFields(providerWebsite?: string): AuthTemplateFieldGroup[] {
|
||||
return [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
key: 'base_url',
|
||||
label: '站点地址',
|
||||
type: 'text',
|
||||
placeholder: providerWebsite || 'https://cubence.com',
|
||||
required: !providerWebsite,
|
||||
helpText: '通常为 https://cubence.com',
|
||||
},
|
||||
{
|
||||
key: 'token_cookie',
|
||||
label: 'Token Cookie',
|
||||
type: 'password',
|
||||
placeholder: 'token=eyJhbGciOiJI...',
|
||||
required: true,
|
||||
sensitive: true,
|
||||
helpText: '从浏览器开发者工具复制 Cookie 中的 token 值(JWT 格式)',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest {
|
||||
const baseUrl = formData.base_url || providerWebsite || ''
|
||||
|
||||
return {
|
||||
architecture_id: 'cubence',
|
||||
base_url: baseUrl,
|
||||
connector: {
|
||||
auth_type: 'cookie',
|
||||
config: {},
|
||||
credentials: {
|
||||
token_cookie: formData.token_cookie,
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
schedule: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseConfig(config: any): Record<string, any> {
|
||||
return {
|
||||
base_url: config?.base_url || '',
|
||||
token_cookie: config?.connector?.credentials?.token_cookie || '',
|
||||
}
|
||||
},
|
||||
|
||||
validate(formData: Record<string, any>): string | null {
|
||||
if (!formData.token_cookie?.trim()) {
|
||||
return '请填写 Token Cookie'
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
formatQuota(quota: number): string {
|
||||
// Cubence 的余额单位直接是美元
|
||||
if (quota >= 1) {
|
||||
return `$${quota.toFixed(2)}`
|
||||
}
|
||||
return `$${quota.toFixed(4)}`
|
||||
},
|
||||
|
||||
formatBalanceExtra(extra: Record<string, any>): BalanceExtraItem[] {
|
||||
const items: BalanceExtraItem[] = []
|
||||
|
||||
// 5小时窗口限额
|
||||
if (extra.five_hour_limit) {
|
||||
const limit = extra.five_hour_limit
|
||||
const detail = formatWindowLimitDetail(limit)
|
||||
const { text, percent } = formatWindowLimit(limit)
|
||||
items.push({
|
||||
label: '5h',
|
||||
value: text,
|
||||
percent,
|
||||
resetsAt: limit.resets_at,
|
||||
tooltip: detail || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// 每周窗口限额
|
||||
if (extra.weekly_limit) {
|
||||
const limit = extra.weekly_limit
|
||||
const detail = formatWindowLimitDetail(limit)
|
||||
const { text, percent } = formatWindowLimit(limit)
|
||||
items.push({
|
||||
label: '周',
|
||||
value: text,
|
||||
percent,
|
||||
resetsAt: limit.resets_at,
|
||||
tooltip: detail || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
},
|
||||
}
|
||||
@@ -22,12 +22,15 @@
|
||||
*/
|
||||
|
||||
import type { AuthTemplate, AuthTemplateRegistry } from './types'
|
||||
import { anyrouterTemplate } from './anyrouter'
|
||||
import { cubenceTemplate } from './cubence'
|
||||
import { newApiTemplate } from './new-api'
|
||||
import { yescodeTemplate } from './yescode'
|
||||
|
||||
// ==================== 模板注册 ====================
|
||||
// 在这里添加新模板
|
||||
|
||||
const templates: AuthTemplate[] = [newApiTemplate]
|
||||
const templates: AuthTemplate[] = [newApiTemplate, anyrouterTemplate, cubenceTemplate, yescodeTemplate]
|
||||
|
||||
// ==================== 注册表实现 ====================
|
||||
|
||||
@@ -63,4 +66,7 @@ export const authTemplateRegistry: AuthTemplateRegistry = {
|
||||
// ==================== 导出 ====================
|
||||
|
||||
export * from './types'
|
||||
export { anyrouterTemplate } from './anyrouter'
|
||||
export { cubenceTemplate } from './cubence'
|
||||
export { newApiTemplate } from './new-api'
|
||||
export { yescodeTemplate } from './yescode'
|
||||
|
||||
@@ -103,6 +103,29 @@ export interface AuthTemplate {
|
||||
* @param quota quota 值
|
||||
*/
|
||||
formatQuota?(quota: number): string
|
||||
|
||||
/**
|
||||
* 格式化余额 extra 信息(如窗口限额等)
|
||||
* 返回一个数组,每个元素包含 label 和 value
|
||||
* @param extra 余额 extra 字段
|
||||
*/
|
||||
formatBalanceExtra?(extra: Record<string, any>): BalanceExtraItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 余额附加信息项
|
||||
*/
|
||||
export interface BalanceExtraItem {
|
||||
/** 显示标签 */
|
||||
label: string
|
||||
/** 显示值 */
|
||||
value: string
|
||||
/** 百分比数值 (0-100),用于进度条显示 */
|
||||
percent?: number
|
||||
/** 重置时间戳(Unix 秒),用于倒计时显示 */
|
||||
resetsAt?: number
|
||||
/** 可选的提示文本 */
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
135
frontend/src/features/providers/auth-templates/yescode.ts
Normal file
135
frontend/src/features/providers/auth-templates/yescode.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* YesCode 认证模板
|
||||
*
|
||||
* 适用于 YesCode 中转站:
|
||||
* - 使用 Cookie 认证(yescode_auth JWT + yescode_csrf)
|
||||
* - 余额单位直接是美元
|
||||
* - subscription_balance: 套餐每日额度
|
||||
* - weekly_limit: 周限额(用完后套餐额度也无法使用)
|
||||
*/
|
||||
|
||||
import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types'
|
||||
import type { SaveConfigRequest } from '@/api/providerOps'
|
||||
|
||||
/**
|
||||
* 格式化限额显示(百分比格式)
|
||||
*/
|
||||
function formatLimitPercent(remaining: number, limit: number): { text: string; percent: number } {
|
||||
if (!limit || limit === 0) {
|
||||
return { text: '-', percent: 0 }
|
||||
}
|
||||
|
||||
const percent = Math.round((remaining / limit) * 100)
|
||||
return { text: `${percent}%`, percent }
|
||||
}
|
||||
|
||||
export const yescodeTemplate: AuthTemplate = {
|
||||
id: 'yescode',
|
||||
name: 'YesCode',
|
||||
description: '适用于 YesCode 中转站,使用 Cookie 认证',
|
||||
|
||||
getFields(providerWebsite?: string): AuthTemplateFieldGroup[] {
|
||||
return [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
key: 'base_url',
|
||||
label: '站点地址',
|
||||
type: 'text',
|
||||
placeholder: providerWebsite || 'https://co.yes.vg',
|
||||
required: !providerWebsite,
|
||||
helpText: '通常为 https://co.yes.vg',
|
||||
},
|
||||
{
|
||||
key: 'auth_cookie',
|
||||
label: 'Auth Cookie',
|
||||
type: 'password',
|
||||
placeholder: 'yescode_auth=eyJhbGciOiJI...; yescode_csrf=...',
|
||||
required: true,
|
||||
sensitive: true,
|
||||
helpText: '从浏览器开发者工具复制 Cookie(包含 yescode_auth 和 yescode_csrf)',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest {
|
||||
const baseUrl = formData.base_url || providerWebsite || ''
|
||||
|
||||
return {
|
||||
architecture_id: 'yescode',
|
||||
base_url: baseUrl,
|
||||
connector: {
|
||||
auth_type: 'cookie',
|
||||
config: {},
|
||||
credentials: {
|
||||
auth_cookie: formData.auth_cookie,
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
schedule: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseConfig(config: any): Record<string, any> {
|
||||
return {
|
||||
base_url: config?.base_url || '',
|
||||
auth_cookie: config?.connector?.credentials?.auth_cookie || '',
|
||||
}
|
||||
},
|
||||
|
||||
validate(formData: Record<string, any>): string | null {
|
||||
if (!formData.auth_cookie?.trim()) {
|
||||
return '请填写 Auth Cookie'
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
formatQuota(quota: number): string {
|
||||
// YesCode 的余额单位直接是美元
|
||||
if (quota >= 1) {
|
||||
return `$${quota.toFixed(2)}`
|
||||
}
|
||||
return `$${quota.toFixed(4)}`
|
||||
},
|
||||
|
||||
formatBalanceExtra(extra: Record<string, any>): BalanceExtraItem[] {
|
||||
const items: BalanceExtraItem[] = []
|
||||
|
||||
const weeklyLimit = extra.weekly_limit
|
||||
const weeklySpent = extra.weekly_spent
|
||||
const dailyLimit = extra.daily_limit
|
||||
const dailySpent = extra.daily_spent
|
||||
const dailyResetsAt = extra.daily_resets_at
|
||||
const weeklyResetsAt = extra.weekly_resets_at
|
||||
|
||||
// 天限
|
||||
if (dailyLimit !== undefined && dailyLimit > 0 && dailySpent !== undefined) {
|
||||
const dailyRemaining = Math.max(0, dailyLimit - dailySpent)
|
||||
const { text, percent } = formatLimitPercent(dailyRemaining, dailyLimit)
|
||||
items.push({
|
||||
label: '天',
|
||||
value: text,
|
||||
percent,
|
||||
resetsAt: dailyResetsAt,
|
||||
tooltip: `$${dailyRemaining.toFixed(2)} / $${(dailyLimit as number).toFixed(2)}`,
|
||||
})
|
||||
}
|
||||
|
||||
// 周限
|
||||
if (weeklyLimit !== undefined && weeklyLimit > 0 && weeklySpent !== undefined) {
|
||||
const weeklyRemaining = Math.max(0, weeklyLimit - weeklySpent)
|
||||
const { text, percent } = formatLimitPercent(weeklyRemaining, weeklyLimit)
|
||||
items.push({
|
||||
label: '周',
|
||||
value: text,
|
||||
percent,
|
||||
resetsAt: weeklyResetsAt,
|
||||
tooltip: `$${weeklyRemaining.toFixed(2)} / $${(weeklyLimit as number).toFixed(2)}`,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
},
|
||||
}
|
||||
@@ -175,7 +175,7 @@ import {
|
||||
} from '../auth-templates'
|
||||
|
||||
// 敏感字段列表(用于验证和加载配置时的特殊处理)
|
||||
const SENSITIVE_FIELDS = ['api_key', 'password', 'session_token', 'cookie_string', 'cookies'] as const
|
||||
const SENSITIVE_FIELDS = ['api_key', 'password', 'session_token', 'session_cookie', 'token_cookie', 'auth_cookie', 'cookie_string', 'cookies'] as const
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
@@ -427,11 +427,11 @@ function loadFromConfig(config: any) {
|
||||
|
||||
hasExistingConfig.value = true
|
||||
|
||||
// 目前只支持 new_api 模板
|
||||
selectedTemplateId.value = 'new_api'
|
||||
|
||||
// 使用模板解析配置
|
||||
// 根据已保存的 architecture_id 选择对应模板,不存在则回退到 new_api
|
||||
const architectureId = config.architecture_id || 'new_api'
|
||||
selectedTemplateId.value = authTemplateRegistry.get(architectureId) ? architectureId : 'new_api'
|
||||
const template = authTemplateRegistry.get(selectedTemplateId.value)
|
||||
|
||||
if (template) {
|
||||
const parsedData = template.parseConfig(config)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user