mirror of
https://github.com/TheSmallHanCat/sora2api.git
synced 2026-03-12 23:07:32 +08:00
feat(pow): 新增计算pow支持传入token
This commit is contained in:
@@ -64,6 +64,10 @@ timezone_offset = 8
|
||||
# beta测试,目前仍处于测试阶段
|
||||
# POW 计算模式:local(本地计算)或 external(外部服务)
|
||||
mode = "external"
|
||||
# 是否使用对应 token 进行 POW 计算(默认关闭)
|
||||
# local 模式开启后会使用当前轮询 token 获取 POW
|
||||
# external 模式开启后会向外部服务传递 accesstoken 字段
|
||||
use_token_for_pow = false
|
||||
# 外部 POW 服务地址(仅在 external 模式下使用)
|
||||
server_url = "http://localhost:8002"
|
||||
# 外部 POW 服务访问密钥(仅在 external 模式下使用)
|
||||
|
||||
@@ -173,6 +173,7 @@ class UpdatePowProxyConfigRequest(BaseModel):
|
||||
|
||||
class UpdatePowServiceConfigRequest(BaseModel):
|
||||
mode: str # "local" or "external"
|
||||
use_token_for_pow: Optional[bool] = False
|
||||
server_url: Optional[str] = None
|
||||
api_key: Optional[str] = None
|
||||
proxy_enabled: Optional[bool] = None
|
||||
@@ -1408,6 +1409,7 @@ async def update_pow_proxy_config(
|
||||
config_obj = await db.get_pow_service_config()
|
||||
await db.update_pow_service_config(
|
||||
mode=config_obj.mode,
|
||||
use_token_for_pow=config_obj.use_token_for_pow,
|
||||
server_url=config_obj.server_url,
|
||||
api_key=config_obj.api_key,
|
||||
proxy_enabled=request.pow_proxy_enabled,
|
||||
@@ -1432,6 +1434,7 @@ async def get_pow_service_config(token: str = Depends(verify_admin_token)) -> di
|
||||
"success": True,
|
||||
"config": {
|
||||
"mode": config_obj.mode,
|
||||
"use_token_for_pow": config_obj.use_token_for_pow,
|
||||
"server_url": config_obj.server_url or "",
|
||||
"api_key": config_obj.api_key or "",
|
||||
"proxy_enabled": config_obj.proxy_enabled,
|
||||
@@ -1448,6 +1451,7 @@ async def update_pow_service_config(
|
||||
try:
|
||||
await db.update_pow_service_config(
|
||||
mode=request.mode,
|
||||
use_token_for_pow=request.use_token_for_pow or False,
|
||||
server_url=request.server_url,
|
||||
api_key=request.api_key,
|
||||
proxy_enabled=request.proxy_enabled,
|
||||
@@ -1455,6 +1459,7 @@ async def update_pow_service_config(
|
||||
)
|
||||
# Update runtime config
|
||||
config.set_pow_service_mode(request.mode)
|
||||
config.set_pow_service_use_token_for_pow(request.use_token_for_pow or False)
|
||||
config.set_pow_service_server_url(request.server_url or "")
|
||||
config.set_pow_service_api_key(request.api_key or "")
|
||||
config.set_pow_service_proxy_enabled(request.proxy_enabled or False)
|
||||
|
||||
@@ -285,6 +285,17 @@ class Config:
|
||||
self._config["pow_service"] = {}
|
||||
self._config["pow_service"]["mode"] = mode
|
||||
|
||||
@property
|
||||
def pow_service_use_token_for_pow(self) -> bool:
|
||||
"""Whether to use current token for POW calculation"""
|
||||
return self._config.get("pow_service", {}).get("use_token_for_pow", False)
|
||||
|
||||
def set_pow_service_use_token_for_pow(self, enabled: bool):
|
||||
"""Set whether to use current token for POW calculation"""
|
||||
if "pow_service" not in self._config:
|
||||
self._config["pow_service"] = {}
|
||||
self._config["pow_service"]["use_token_for_pow"] = enabled
|
||||
|
||||
@property
|
||||
def pow_service_server_url(self) -> str:
|
||||
"""Get POW service server URL"""
|
||||
|
||||
@@ -230,6 +230,7 @@ class Database:
|
||||
if count[0] == 0:
|
||||
# Get POW service config from config_dict if provided, otherwise use defaults
|
||||
mode = "local"
|
||||
use_token_for_pow = False
|
||||
server_url = None
|
||||
api_key = None
|
||||
proxy_enabled = False
|
||||
@@ -238,6 +239,7 @@ class Database:
|
||||
if config_dict:
|
||||
pow_service_config = config_dict.get("pow_service", {})
|
||||
mode = pow_service_config.get("mode", "local")
|
||||
use_token_for_pow = pow_service_config.get("use_token_for_pow", False)
|
||||
server_url = pow_service_config.get("server_url", "")
|
||||
api_key = pow_service_config.get("api_key", "")
|
||||
proxy_enabled = pow_service_config.get("proxy_enabled", False)
|
||||
@@ -248,9 +250,9 @@ class Database:
|
||||
proxy_url = proxy_url if proxy_url else None
|
||||
|
||||
await db.execute("""
|
||||
INSERT INTO pow_service_config (id, mode, server_url, api_key, proxy_enabled, proxy_url)
|
||||
VALUES (1, ?, ?, ?, ?, ?)
|
||||
""", (mode, server_url, api_key, proxy_enabled, proxy_url))
|
||||
INSERT INTO pow_service_config (id, mode, use_token_for_pow, server_url, api_key, proxy_enabled, proxy_url)
|
||||
VALUES (1, ?, ?, ?, ?, ?, ?)
|
||||
""", (mode, use_token_for_pow, server_url, api_key, proxy_enabled, proxy_url))
|
||||
|
||||
|
||||
async def check_and_migrate_db(self, config_dict: dict = None):
|
||||
@@ -319,6 +321,35 @@ class Database:
|
||||
except Exception as e:
|
||||
print(f" ✗ Failed to add column '{col_name}': {e}")
|
||||
|
||||
# Check and add missing columns to pow_service_config table
|
||||
if await self._table_exists(db, "pow_service_config"):
|
||||
added_use_token_for_pow_column = False
|
||||
columns_to_add = [
|
||||
("use_token_for_pow", "BOOLEAN DEFAULT 0"),
|
||||
]
|
||||
|
||||
for col_name, col_type in columns_to_add:
|
||||
if not await self._column_exists(db, "pow_service_config", col_name):
|
||||
try:
|
||||
await db.execute(f"ALTER TABLE pow_service_config ADD COLUMN {col_name} {col_type}")
|
||||
print(f" ✓ Added column '{col_name}' to pow_service_config table")
|
||||
if col_name == "use_token_for_pow":
|
||||
added_use_token_for_pow_column = True
|
||||
except Exception as e:
|
||||
print(f" ✗ Failed to add column '{col_name}': {e}")
|
||||
|
||||
# On upgrade, initialize value from setting.toml only when this column is newly added
|
||||
if config_dict and added_use_token_for_pow_column:
|
||||
try:
|
||||
use_token_for_pow = config_dict.get("pow_service", {}).get("use_token_for_pow", False)
|
||||
await db.execute("""
|
||||
UPDATE pow_service_config
|
||||
SET use_token_for_pow = ?
|
||||
WHERE id = 1
|
||||
""", (use_token_for_pow,))
|
||||
except Exception as e:
|
||||
print(f" ✗ Failed to initialize use_token_for_pow from config: {e}")
|
||||
|
||||
# Check and add missing columns to watermark_free_config table
|
||||
if await self._table_exists(db, "watermark_free_config"):
|
||||
columns_to_add = [
|
||||
@@ -551,6 +582,7 @@ class Database:
|
||||
CREATE TABLE IF NOT EXISTS pow_service_config (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
mode TEXT DEFAULT 'local',
|
||||
use_token_for_pow BOOLEAN DEFAULT 0,
|
||||
server_url TEXT,
|
||||
api_key TEXT,
|
||||
proxy_enabled BOOLEAN DEFAULT 0,
|
||||
@@ -1354,6 +1386,7 @@ class Database:
|
||||
return PowServiceConfig(**dict(row))
|
||||
return PowServiceConfig(
|
||||
mode="local",
|
||||
use_token_for_pow=False,
|
||||
server_url=None,
|
||||
api_key=None,
|
||||
proxy_enabled=False,
|
||||
@@ -1373,6 +1406,7 @@ class Database:
|
||||
async def update_pow_service_config(
|
||||
self,
|
||||
mode: str,
|
||||
use_token_for_pow: bool = False,
|
||||
server_url: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
proxy_enabled: Optional[bool] = None,
|
||||
@@ -1382,9 +1416,9 @@ class Database:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
# Use INSERT OR REPLACE to ensure the row exists
|
||||
await db.execute("""
|
||||
INSERT OR REPLACE INTO pow_service_config (id, mode, server_url, api_key, proxy_enabled, proxy_url, updated_at)
|
||||
VALUES (1, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
""", (mode, server_url, api_key, proxy_enabled, proxy_url))
|
||||
INSERT OR REPLACE INTO pow_service_config (id, mode, use_token_for_pow, server_url, api_key, proxy_enabled, proxy_url, updated_at)
|
||||
VALUES (1, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
""", (mode, use_token_for_pow, server_url, api_key, proxy_enabled, proxy_url))
|
||||
await db.commit()
|
||||
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ class PowServiceConfig(BaseModel):
|
||||
"""POW service configuration"""
|
||||
id: int = 1
|
||||
mode: str = "local" # "local" or "external"
|
||||
use_token_for_pow: bool = False # Whether to use current token for POW calculation
|
||||
server_url: Optional[str] = None # External POW service URL
|
||||
api_key: Optional[str] = None # External POW service API key
|
||||
proxy_enabled: bool = False # Whether to enable proxy for POW service
|
||||
|
||||
@@ -147,11 +147,12 @@ async def startup_event():
|
||||
# Load POW service configuration from database
|
||||
pow_service_config = await db.get_pow_service_config()
|
||||
config.set_pow_service_mode(pow_service_config.mode)
|
||||
config.set_pow_service_use_token_for_pow(pow_service_config.use_token_for_pow)
|
||||
config.set_pow_service_server_url(pow_service_config.server_url or "")
|
||||
config.set_pow_service_api_key(pow_service_config.api_key or "")
|
||||
config.set_pow_service_proxy_enabled(pow_service_config.proxy_enabled)
|
||||
config.set_pow_service_proxy_url(pow_service_config.proxy_url or "")
|
||||
print(f"✓ POW service mode: {pow_service_config.mode}")
|
||||
print(f"✓ POW service mode: {pow_service_config.mode}, use_token_for_pow: {pow_service_config.use_token_for_pow}")
|
||||
|
||||
# Initialize concurrency manager with all tokens
|
||||
all_tokens = await db.get_all_tokens()
|
||||
|
||||
@@ -39,24 +39,59 @@ class POWServiceClient:
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Accept": "application/json"
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# Add access_token to headers if provided
|
||||
if access_token:
|
||||
headers["X-Access-Token"] = access_token
|
||||
# Controlled by config switch: whether to pass current token to POW service
|
||||
send_access_token = bool(config.pow_service_use_token_for_pow and access_token)
|
||||
|
||||
def _mask_token(token_value: Optional[str]) -> str:
|
||||
if not token_value:
|
||||
return "none"
|
||||
if len(token_value) <= 10:
|
||||
return "***"
|
||||
return f"{token_value[:6]}...{token_value[-4:]}"
|
||||
|
||||
debug_logger.log_info(
|
||||
f"[POW Service] use_token_for_pow={config.pow_service_use_token_for_pow}, access_token={_mask_token(access_token)}"
|
||||
)
|
||||
|
||||
try:
|
||||
debug_logger.log_info(f"[POW Service] Requesting token from {api_url}")
|
||||
|
||||
async with AsyncSession(impersonate="chrome131") as session:
|
||||
response = await session.get(
|
||||
# Preferred protocol: POST + JSON body
|
||||
payload = {"flow": "sora_init"}
|
||||
if send_access_token:
|
||||
payload["accesstoken"] = access_token
|
||||
|
||||
response = await session.post(
|
||||
api_url,
|
||||
headers=headers,
|
||||
json=payload,
|
||||
proxy=proxy_url,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# Backward compatibility: older services may only support GET + X-Access-Token
|
||||
if response.status_code in (404, 405, 415):
|
||||
fallback_headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
if send_access_token:
|
||||
fallback_headers["X-Access-Token"] = access_token
|
||||
debug_logger.log_info(
|
||||
f"[POW Service] POST unsupported ({response.status_code}), fallback to GET compatibility mode"
|
||||
)
|
||||
response = await session.get(
|
||||
api_url,
|
||||
headers=fallback_headers,
|
||||
proxy=proxy_url,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
error_msg = f"POW service request failed: {response.status_code}"
|
||||
debug_logger.log_error(
|
||||
|
||||
@@ -32,7 +32,7 @@ _playwright = None
|
||||
_current_proxy = None
|
||||
|
||||
# Sentinel token cache
|
||||
_cached_sentinel_token = None
|
||||
_cached_sentinel_token_map = {}
|
||||
_cached_device_id = None
|
||||
|
||||
|
||||
@@ -245,13 +245,19 @@ async def _get_cached_sentinel_token(proxy_url: str = None, force_refresh: bool
|
||||
Raises:
|
||||
Exception: If 403/429 when fetching oai-did
|
||||
"""
|
||||
global _cached_sentinel_token
|
||||
global _cached_sentinel_token_map
|
||||
|
||||
# Whether current request should be token-aware for POW
|
||||
use_token_for_pow = bool(config.pow_service_use_token_for_pow and access_token)
|
||||
cache_key = access_token if use_token_for_pow else "__default__"
|
||||
|
||||
# Check if external POW service is configured
|
||||
if config.pow_service_mode == "external":
|
||||
debug_logger.log_info("[POW] Using external POW service (cached sentinel)")
|
||||
from .pow_service_client import pow_service_client
|
||||
result = await pow_service_client.get_sentinel_token(access_token=access_token)
|
||||
result = await pow_service_client.get_sentinel_token(
|
||||
access_token=access_token if use_token_for_pow else None
|
||||
)
|
||||
|
||||
if result:
|
||||
sentinel_token, device_id, service_user_agent = result
|
||||
@@ -263,25 +269,36 @@ async def _get_cached_sentinel_token(proxy_url: str = None, force_refresh: bool
|
||||
|
||||
# Local mode (original logic)
|
||||
# Return cached token if available and not forcing refresh
|
||||
if _cached_sentinel_token and not force_refresh:
|
||||
debug_logger.log_info("[Sentinel] Using cached token")
|
||||
return _cached_sentinel_token
|
||||
if not force_refresh and cache_key in _cached_sentinel_token_map:
|
||||
if use_token_for_pow:
|
||||
debug_logger.log_info("[Sentinel] Using token-scoped cached token")
|
||||
else:
|
||||
debug_logger.log_info("[Sentinel] Using shared cached token")
|
||||
return _cached_sentinel_token_map[cache_key]
|
||||
|
||||
# Generate new token
|
||||
debug_logger.log_info("[Sentinel] Generating new token...")
|
||||
token = await _generate_sentinel_token_lightweight(proxy_url)
|
||||
|
||||
if token:
|
||||
_cached_sentinel_token = token
|
||||
_cached_sentinel_token_map[cache_key] = token
|
||||
debug_logger.log_info("[Sentinel] Token cached successfully")
|
||||
|
||||
return token
|
||||
|
||||
|
||||
def _invalidate_sentinel_cache():
|
||||
"""Invalidate cached sentinel token (call after 400 error)"""
|
||||
global _cached_sentinel_token
|
||||
_cached_sentinel_token = None
|
||||
def _invalidate_sentinel_cache(access_token: Optional[str] = None):
|
||||
"""Invalidate cached sentinel token (call after 400 error)
|
||||
|
||||
Args:
|
||||
access_token: Optional current access token for token-scoped cache invalidation
|
||||
"""
|
||||
global _cached_sentinel_token_map
|
||||
use_token_for_pow = bool(config.pow_service_use_token_for_pow and access_token)
|
||||
cache_key = access_token if use_token_for_pow else "__default__"
|
||||
|
||||
if cache_key in _cached_sentinel_token_map:
|
||||
del _cached_sentinel_token_map[cache_key]
|
||||
debug_logger.log_info("[Sentinel] Cache invalidated")
|
||||
|
||||
|
||||
@@ -755,7 +772,9 @@ class SoraClient:
|
||||
# Check if external POW service is configured
|
||||
if config.pow_service_mode == "external":
|
||||
debug_logger.log_info("[Sentinel] Using external POW service...")
|
||||
result = await pow_service_client.get_sentinel_token(access_token=token)
|
||||
result = await pow_service_client.get_sentinel_token(
|
||||
access_token=token if config.pow_service_use_token_for_pow else None
|
||||
)
|
||||
|
||||
if result:
|
||||
sentinel_token, device_id, service_user_agent = result
|
||||
@@ -1173,7 +1192,7 @@ class SoraClient:
|
||||
debug_logger.log_info("[Sentinel] Got 400 error, refreshing token and retrying...")
|
||||
|
||||
# Invalidate cache and get fresh token
|
||||
_invalidate_sentinel_cache()
|
||||
_invalidate_sentinel_cache(token)
|
||||
|
||||
try:
|
||||
sentinel_token = await _get_cached_sentinel_token(pow_proxy_url, force_refresh=True, access_token=token)
|
||||
|
||||
@@ -402,6 +402,13 @@
|
||||
</select>
|
||||
<p class="text-xs text-muted-foreground mt-1">选择 POW 计算方式</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" id="cfgPowUseTokenForPow" class="h-4 w-4 rounded border-input">
|
||||
<span class="text-sm font-medium">使用对应 Token 进行计算</span>
|
||||
</label>
|
||||
<p class="text-xs text-muted-foreground mt-1">默认关闭。local 模式下使用当前轮询 Token 计算;external 模式下会传递 accesstoken 字段。</p>
|
||||
</div>
|
||||
<div id="powExternalFields" style="display: none;">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
@@ -1142,8 +1149,8 @@
|
||||
deleteCharacter=async(id)=>{if(!confirm('确定要删除这个角色卡吗?'))return;try{const r=await apiRequest(`/api/characters/${id}`,{method:'DELETE'});if(!r)return;const d=await r.json();if(d.success){showToast('删除成功','success');await loadCharacters()}else{showToast('删除失败','error')}}catch(e){showToast('删除失败: '+e.message,'error')}},
|
||||
loadCallLogicConfig=async()=>{try{const r=await apiRequest('/api/call-logic/config');if(!r)return;const d=await r.json();if(d.success&&d.config){const mode=d.config.call_mode||((d.config.polling_mode_enabled||false)?'polling':'default');$('cfgCallLogicMode').value=mode}else{console.error('调用逻辑配置数据格式错误:',d)}}catch(e){console.error('加载调用逻辑配置失败:',e)}},
|
||||
saveCallLogicConfig=async()=>{try{const mode=$('cfgCallLogicMode').value||'default';const r=await apiRequest('/api/call-logic/config',{method:'POST',body:JSON.stringify({call_mode:mode})});if(!r)return;const d=await r.json();if(d.success){showToast('调用逻辑配置保存成功','success')}else{showToast('保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
|
||||
loadPowConfig=async()=>{try{const r=await apiRequest('/api/pow/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('cfgPowMode').value=d.config.mode||'local';$('cfgPowServerUrl').value=d.config.server_url||'';$('cfgPowApiKey').value=d.config.api_key||'';$('cfgPowProxyEnabled').checked=d.config.proxy_enabled||false;$('cfgPowProxyUrl').value=d.config.proxy_url||'';togglePowFields();togglePowProxyFields()}else{console.error('POW配置数据格式错误:',d)}}catch(e){console.error('加载POW配置失败:',e)}},
|
||||
savePowConfig=async()=>{try{const mode=$('cfgPowMode').value;const serverUrl=$('cfgPowServerUrl').value.trim();const apiKey=$('cfgPowApiKey').value.trim();const proxyEnabled=$('cfgPowProxyEnabled').checked;const proxyUrl=$('cfgPowProxyUrl').value.trim();if(mode==='external'){if(!serverUrl)return showToast('请输入服务器地址','error');if(!apiKey)return showToast('请输入API密钥','error')}const r=await apiRequest('/api/pow/config',{method:'POST',body:JSON.stringify({mode:mode,server_url:serverUrl||null,api_key:apiKey||null,proxy_enabled:proxyEnabled,proxy_url:proxyUrl||null})});if(!r)return;const d=await r.json();if(d.success){showToast('POW配置保存成功','success')}else{showToast('保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
|
||||
loadPowConfig=async()=>{try{const r=await apiRequest('/api/pow/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('cfgPowMode').value=d.config.mode||'local';$('cfgPowUseTokenForPow').checked=d.config.use_token_for_pow||false;$('cfgPowServerUrl').value=d.config.server_url||'';$('cfgPowApiKey').value=d.config.api_key||'';$('cfgPowProxyEnabled').checked=d.config.proxy_enabled||false;$('cfgPowProxyUrl').value=d.config.proxy_url||'';togglePowFields();togglePowProxyFields()}else{console.error('POW配置数据格式错误:',d)}}catch(e){console.error('加载POW配置失败:',e)}},
|
||||
savePowConfig=async()=>{try{const mode=$('cfgPowMode').value;const useTokenForPow=$('cfgPowUseTokenForPow').checked;const serverUrl=$('cfgPowServerUrl').value.trim();const apiKey=$('cfgPowApiKey').value.trim();const proxyEnabled=$('cfgPowProxyEnabled').checked;const proxyUrl=$('cfgPowProxyUrl').value.trim();if(mode==='external'){if(!serverUrl)return showToast('请输入服务器地址','error');if(!apiKey)return showToast('请输入API密钥','error')}const r=await apiRequest('/api/pow/config',{method:'POST',body:JSON.stringify({mode:mode,use_token_for_pow:useTokenForPow,server_url:serverUrl||null,api_key:apiKey||null,proxy_enabled:proxyEnabled,proxy_url:proxyUrl||null})});if(!r)return;const d=await r.json();if(d.success){showToast('POW配置保存成功','success')}else{showToast('保存失败','error')}}catch(e){showToast('保存失败: '+e.message,'error')}},
|
||||
loadPowProxyConfig=loadPowConfig,savePowProxyConfig=savePowConfig,loadPowServiceConfig=loadPowConfig,savePowServiceConfig=savePowConfig,
|
||||
togglePowFields=()=>{const mode=$('cfgPowMode').value;const externalFields=$('powExternalFields');if(externalFields){externalFields.style.display=mode==='external'?'block':'none'}},
|
||||
togglePowProxyFields=()=>{const enabled=$('cfgPowProxyEnabled').checked;const proxyUrlField=$('powProxyUrlField');if(proxyUrlField){proxyUrlField.style.display=enabled?'block':'none'}},
|
||||
|
||||
Reference in New Issue
Block a user