2026-01-19 17:18:03 +08:00
|
|
|
|
"""
|
|
|
|
|
|
Cubence 架构
|
|
|
|
|
|
|
|
|
|
|
|
针对 Cubence 中转站的预设配置。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
from typing import Any
|
2026-01-19 17:18:03 +08:00
|
|
|
|
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
|
|
|
|
|
|
from src.services.provider_ops.actions import ProviderAction
|
|
|
|
|
|
from src.services.provider_ops.actions.cubence_balance import CubenceBalanceAction
|
|
|
|
|
|
from src.services.provider_ops.architectures.base import (
|
|
|
|
|
|
ProviderArchitecture,
|
|
|
|
|
|
ProviderConnector,
|
|
|
|
|
|
VerifyResult,
|
|
|
|
|
|
)
|
|
|
|
|
|
from src.services.provider_ops.types import ConnectorAuthType, ProviderActionType
|
2026-02-13 16:41:30 +08:00
|
|
|
|
from src.services.provider_ops.utils import extract_cookie_value
|
2026-01-19 17:18:03 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CubenceConnector(ProviderConnector):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Cubence 专用连接器
|
|
|
|
|
|
|
|
|
|
|
|
特点:
|
|
|
|
|
|
- 使用 Cookie 认证(token JWT)
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
auth_type = ConnectorAuthType.COOKIE
|
|
|
|
|
|
display_name = "Cubence Cookie"
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def __init__(self, base_url: str, config: dict[str, Any] | None = None):
|
2026-01-19 17:18:03 +08:00
|
|
|
|
super().__init__(base_url, config)
|
2026-01-30 03:10:21 +08:00
|
|
|
|
self._token_cookie: str | None = None
|
2026-01-19 17:18:03 +08:00
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def connect(self, credentials: dict[str, Any]) -> bool:
|
2026-01-19 17:18:03 +08:00
|
|
|
|
"""建立连接"""
|
|
|
|
|
|
token_cookie = credentials.get("token_cookie")
|
|
|
|
|
|
if not token_cookie:
|
|
|
|
|
|
self._set_error("Token Cookie 不能为空")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
# 提取纯 token 值
|
2026-02-13 16:41:30 +08:00
|
|
|
|
self._token_cookie = extract_cookie_value(token_cookie, "token")
|
2026-01-19 17:18:03 +08:00
|
|
|
|
|
|
|
|
|
|
self._set_connected()
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
async def disconnect(self) -> None:
|
|
|
|
|
|
"""断开连接"""
|
|
|
|
|
|
self._token_cookie = None
|
|
|
|
|
|
self._set_disconnected()
|
|
|
|
|
|
|
|
|
|
|
|
async def is_authenticated(self) -> bool:
|
|
|
|
|
|
"""检查是否已认证"""
|
|
|
|
|
|
return self._token_cookie is not None
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_auth(self, request: httpx.Request) -> httpx.Request:
|
|
|
|
|
|
"""为请求应用认证信息"""
|
|
|
|
|
|
if self._token_cookie:
|
|
|
|
|
|
request.headers["Cookie"] = f"token={self._token_cookie}"
|
|
|
|
|
|
|
|
|
|
|
|
return request
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_credentials_schema(cls) -> dict[str, Any]:
|
2026-01-19 17:18:03 +08:00
|
|
|
|
"""获取凭据配置 schema"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
"type": "object",
|
|
|
|
|
|
"properties": {
|
2026-02-13 16:41:30 +08:00
|
|
|
|
"base_url": {
|
|
|
|
|
|
"type": "string",
|
|
|
|
|
|
"title": "站点地址",
|
|
|
|
|
|
"description": "API 基础地址",
|
|
|
|
|
|
"x-default-value": "https://cubence.com",
|
|
|
|
|
|
},
|
2026-01-19 17:18:03 +08:00
|
|
|
|
"token_cookie": {
|
|
|
|
|
|
"type": "string",
|
|
|
|
|
|
"title": "Token Cookie",
|
|
|
|
|
|
"description": "从浏览器复制的 token Cookie 值(JWT 格式)",
|
2026-02-13 16:41:30 +08:00
|
|
|
|
"x-sensitive": True,
|
|
|
|
|
|
"x-input-type": "password",
|
2026-01-19 17:18:03 +08:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
"required": ["token_cookie"],
|
2026-02-13 16:41:30 +08:00
|
|
|
|
"x-field-groups": [
|
|
|
|
|
|
{"fields": ["base_url"]},
|
|
|
|
|
|
{"fields": ["token_cookie"]},
|
|
|
|
|
|
],
|
|
|
|
|
|
"x-auth-type": "cookie",
|
|
|
|
|
|
"x-default-base-url": "https://cubence.com",
|
|
|
|
|
|
"x-validation": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"type": "required",
|
|
|
|
|
|
"fields": ["token_cookie"],
|
|
|
|
|
|
"message": "请填写 Token Cookie",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
"x-quota-divisor": None,
|
|
|
|
|
|
"x-currency": "USD",
|
|
|
|
|
|
"x-balance-extra-format": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "5h",
|
|
|
|
|
|
"type": "window_limit",
|
|
|
|
|
|
"source": "five_hour_limit",
|
|
|
|
|
|
"unit_divisor": 1000000,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "周",
|
|
|
|
|
|
"type": "window_limit",
|
|
|
|
|
|
"source": "weekly_limit",
|
|
|
|
|
|
"unit_divisor": 1000000,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-01-19 17:18:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CubenceArchitecture(ProviderArchitecture):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Cubence 架构预设
|
|
|
|
|
|
|
|
|
|
|
|
针对 Cubence 中转站优化的预设配置。
|
|
|
|
|
|
|
|
|
|
|
|
特点:
|
|
|
|
|
|
- 使用 Cookie 认证(token JWT)
|
|
|
|
|
|
- 验证端点: /api/v1/dashboard/overview
|
|
|
|
|
|
- 余额单位直接是美元
|
|
|
|
|
|
- 支持窗口限额(5小时/每周)
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
architecture_id = "cubence"
|
|
|
|
|
|
display_name = "Cubence"
|
|
|
|
|
|
description = "Cubence 中转站预设配置,使用 Cookie 认证"
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
supported_connectors: list[type[ProviderConnector]] = [
|
2026-01-19 17:18:03 +08:00
|
|
|
|
CubenceConnector,
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
supported_actions: list[type[ProviderAction]] = [
|
2026-01-19 17:18:03 +08:00
|
|
|
|
CubenceBalanceAction,
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
default_action_configs: dict[ProviderActionType, dict[str, Any]] = {
|
2026-01-19 17:18:03 +08:00
|
|
|
|
ProviderActionType.QUERY_BALANCE: {
|
|
|
|
|
|
"endpoint": "/api/v1/dashboard/overview",
|
|
|
|
|
|
"method": "GET",
|
|
|
|
|
|
"response_mapping": {
|
|
|
|
|
|
"total_available": "data.balance.total_balance_dollar",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_credentials_schema(self) -> dict[str, Any]:
|
2026-01-19 17:18:03 +08:00
|
|
|
|
"""Cubence 使用 token_cookie 认证"""
|
|
|
|
|
|
return CubenceConnector.get_credentials_schema()
|
|
|
|
|
|
|
|
|
|
|
|
def get_verify_endpoint(self) -> str:
|
|
|
|
|
|
"""验证端点"""
|
|
|
|
|
|
return "/api/v1/dashboard/overview"
|
|
|
|
|
|
|
|
|
|
|
|
def build_verify_headers(
|
|
|
|
|
|
self,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
config: dict[str, Any],
|
|
|
|
|
|
credentials: dict[str, Any],
|
|
|
|
|
|
) -> dict[str, str]:
|
2026-01-19 17:18:03 +08:00
|
|
|
|
"""
|
|
|
|
|
|
构建 Cubence 的验证请求 Headers
|
|
|
|
|
|
|
|
|
|
|
|
使用 Cookie 认证,不使用 Authorization。
|
|
|
|
|
|
"""
|
2026-01-30 03:10:21 +08:00
|
|
|
|
headers: dict[str, str] = {}
|
2026-01-19 17:18:03 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加 token Cookie
|
|
|
|
|
|
cookie_input = credentials.get("token_cookie")
|
|
|
|
|
|
if cookie_input:
|
2026-02-13 16:41:30 +08:00
|
|
|
|
token_value = extract_cookie_value(cookie_input, "token")
|
2026-01-19 17:18:03 +08:00
|
|
|
|
headers["Cookie"] = f"token={token_value}"
|
|
|
|
|
|
|
|
|
|
|
|
return headers
|
|
|
|
|
|
|
2026-02-13 16:41:30 +08:00
|
|
|
|
def _auth_fail_message(self, status_code: int) -> str:
|
|
|
|
|
|
"""Cookie 认证的错误消息"""
|
2026-01-19 17:18:03 +08:00
|
|
|
|
if status_code == 401:
|
2026-02-13 16:41:30 +08:00
|
|
|
|
return "Cookie 已失效,请重新配置"
|
|
|
|
|
|
return "Cookie 已失效或无权限"
|
|
|
|
|
|
|
|
|
|
|
|
def _build_verify_result(
|
|
|
|
|
|
self, user_data: dict[str, Any], raw_data: dict[str, Any] | None = None
|
|
|
|
|
|
) -> VerifyResult:
|
|
|
|
|
|
"""Cubence 自定义字段提取(user/balance/subscription_limits)"""
|
2026-01-19 17:18:03 +08:00
|
|
|
|
user_info = user_data.get("user", {})
|
|
|
|
|
|
balance_info = user_data.get("balance", {})
|
|
|
|
|
|
subscription_limits = user_data.get("subscription_limits", {})
|
|
|
|
|
|
|
|
|
|
|
|
# 构建 extra 信息,包含窗口限额
|
2026-01-30 03:10:21 +08:00
|
|
|
|
extra: dict[str, Any] = {
|
2026-01-19 17:18:03 +08:00
|
|
|
|
"role": user_info.get("role"),
|
|
|
|
|
|
"invite_code": user_info.get("invite_code"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 5小时窗口限额
|
|
|
|
|
|
five_hour = subscription_limits.get("five_hour", {})
|
|
|
|
|
|
if five_hour:
|
|
|
|
|
|
extra["five_hour_limit"] = {
|
|
|
|
|
|
"limit": five_hour.get("limit"),
|
|
|
|
|
|
"used": five_hour.get("used"),
|
|
|
|
|
|
"remaining": five_hour.get("remaining"),
|
|
|
|
|
|
"resets_at": five_hour.get("resets_at"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 每周窗口限额
|
|
|
|
|
|
weekly = subscription_limits.get("weekly", {})
|
|
|
|
|
|
if weekly:
|
|
|
|
|
|
extra["weekly_limit"] = {
|
|
|
|
|
|
"limit": weekly.get("limit"),
|
|
|
|
|
|
"used": weekly.get("used"),
|
|
|
|
|
|
"remaining": weekly.get("remaining"),
|
|
|
|
|
|
"resets_at": weekly.get("resets_at"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return VerifyResult(
|
|
|
|
|
|
success=True,
|
|
|
|
|
|
username=user_info.get("username"),
|
|
|
|
|
|
display_name=user_info.get("username"),
|
|
|
|
|
|
quota=balance_info.get("total_balance_dollar"),
|
|
|
|
|
|
extra=extra,
|
|
|
|
|
|
)
|