更新
This commit is contained in:
+84
-2
@@ -2,6 +2,7 @@ import os
|
||||
import sys
|
||||
import json
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
@@ -93,6 +94,10 @@ from utils.cookie_store import (
|
||||
analyze_cookie,
|
||||
)
|
||||
from rpa_engine.device_profiles import list_device_profiles, profile_label_for_ua, resolve_user_agent
|
||||
from rpa_engine.egress_channels import (
|
||||
clamp_attempts,
|
||||
discover_egress_channels,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("main")
|
||||
from utils import system_logger
|
||||
@@ -443,6 +448,8 @@ def _build_account_im_session(account: Account) -> DouyinImSession:
|
||||
storage = json.loads(cookie_data) if cookie_data else {}
|
||||
session = build_im_session_from_storage(storage, account.im_session_data)
|
||||
session.user_agent = resolve_user_agent(account.user_agent or session.user_agent)
|
||||
session.egress_public_ip = str(account.egress_public_ip or "").strip()
|
||||
session.egress_auto_attempts = clamp_attempts(account.egress_auto_attempts)
|
||||
return session
|
||||
|
||||
|
||||
@@ -806,6 +813,8 @@ class AccountResponse(BaseModel):
|
||||
follow_welcome_content: Optional[str] = None
|
||||
user_agent: Optional[str] = None
|
||||
user_agent_label: Optional[str] = None
|
||||
egress_public_ip: Optional[str] = None
|
||||
egress_auto_attempts: int = 1
|
||||
quota_disabled: bool = False
|
||||
|
||||
class Config:
|
||||
@@ -850,6 +859,10 @@ class AccountUpdate(BaseModel):
|
||||
follow_welcome_enabled: Optional[bool] = None
|
||||
follow_welcome_content: Optional[str] = None
|
||||
user_agent: Optional[str] = None
|
||||
# 空字符串/null=自动选择;否则保存服务器探测到的公网 IPv4。
|
||||
egress_public_ip: Optional[str] = None
|
||||
# 包含首选通道在内的最大串行尝试数,范围 1~8。
|
||||
egress_auto_attempts: Optional[int] = None
|
||||
|
||||
|
||||
class ReplyQueueItemResponse(BaseModel):
|
||||
@@ -1074,6 +1087,8 @@ def _build_account_response(account: Account) -> AccountResponse:
|
||||
follow_welcome_content=account.follow_welcome_content or None,
|
||||
user_agent=account.user_agent or None,
|
||||
user_agent_label=profile_label_for_ua(account.user_agent),
|
||||
egress_public_ip=account.egress_public_ip or None,
|
||||
egress_auto_attempts=clamp_attempts(account.egress_auto_attempts),
|
||||
quota_disabled=bool(account.quota_disabled),
|
||||
)
|
||||
|
||||
@@ -1203,12 +1218,54 @@ class DeviceProfileItem(BaseModel):
|
||||
user_agent: str
|
||||
|
||||
|
||||
class EgressChannelItem(BaseModel):
|
||||
id: str
|
||||
public_ip: str
|
||||
source_ip: Optional[str] = None
|
||||
interface: str = ""
|
||||
is_default: bool = False
|
||||
|
||||
|
||||
class EgressChannelListResponse(BaseModel):
|
||||
channels: List[EgressChannelItem] = Field(default_factory=list)
|
||||
multiple: bool = False
|
||||
detected_at: datetime
|
||||
errors: List[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
@app.get("/api/device-profiles", response_model=List[DeviceProfileItem])
|
||||
async def get_device_profiles(user: User = Depends(get_current_user)):
|
||||
"""可选的伪装设备头(User-Agent)预设列表。"""
|
||||
return list_device_profiles()
|
||||
|
||||
|
||||
@app.get("/api/network/egress-channels", response_model=EgressChannelListResponse)
|
||||
async def get_egress_channels(
|
||||
refresh: bool = False,
|
||||
user: User = Depends(require_accounts_update),
|
||||
):
|
||||
"""Detect bindable server addresses and the public IPv4 seen through each."""
|
||||
|
||||
del user
|
||||
snapshot = await discover_egress_channels(force=refresh)
|
||||
channels = [
|
||||
EgressChannelItem(
|
||||
id=item.id,
|
||||
public_ip=item.public_ip,
|
||||
source_ip=item.source_ip,
|
||||
interface=item.interface,
|
||||
is_default=item.is_default,
|
||||
)
|
||||
for item in snapshot.channels
|
||||
]
|
||||
return EgressChannelListResponse(
|
||||
channels=channels,
|
||||
multiple=len(channels) > 1,
|
||||
detected_at=datetime.fromtimestamp(snapshot.detected_at, tz=timezone.utc),
|
||||
errors=list(snapshot.errors),
|
||||
)
|
||||
|
||||
|
||||
# --- API 路由接口 ---
|
||||
|
||||
# 1. 账号管理接口
|
||||
@@ -1663,6 +1720,18 @@ async def update_account(
|
||||
if body.user_agent is not None:
|
||||
ua = (body.user_agent or "").strip()
|
||||
account.user_agent = ua or None
|
||||
if "egress_public_ip" in body.model_fields_set:
|
||||
selected_public_ip = str(body.egress_public_ip or "").strip()
|
||||
if selected_public_ip:
|
||||
try:
|
||||
parsed_ip = ipaddress.ip_address(selected_public_ip)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="公网通道必须是有效的 IPv4 地址")
|
||||
if parsed_ip.version != 4:
|
||||
raise HTTPException(status_code=400, detail="公网通道目前仅支持 IPv4")
|
||||
account.egress_public_ip = selected_public_ip or None
|
||||
if body.egress_auto_attempts is not None:
|
||||
account.egress_auto_attempts = clamp_attempts(body.egress_auto_attempts)
|
||||
account.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
await db.refresh(account)
|
||||
@@ -1671,6 +1740,13 @@ async def update_account(
|
||||
invalidate = getattr(worker, "invalidate_follow_welcome_config", None)
|
||||
if callable(invalidate):
|
||||
invalidate()
|
||||
worker = manager.workers.get(account_id)
|
||||
runtime_service = getattr(worker, "_im_service", None) if worker else None
|
||||
if runtime_service:
|
||||
runtime_session = runtime_service.session
|
||||
runtime_session.egress_public_ip = str(account.egress_public_ip or "").strip()
|
||||
runtime_session.egress_source_ip = ""
|
||||
runtime_session.egress_auto_attempts = clamp_attempts(account.egress_auto_attempts)
|
||||
return _build_account_response(account)
|
||||
|
||||
|
||||
@@ -2912,6 +2988,9 @@ async def send_account_message(
|
||||
db.add(failed_log)
|
||||
await db.commit()
|
||||
|
||||
normalized_last_error = (last_error or "").upper()
|
||||
session_kicked = "DECISION=KICK" in normalized_last_error
|
||||
invalid_request = "INVALID_REQUEST" in normalized_last_error
|
||||
need_browser = (
|
||||
not session.keys_str
|
||||
or not session.web_protect_str
|
||||
@@ -2919,11 +2998,14 @@ async def send_account_message(
|
||||
or "ticket" in (last_error or "")
|
||||
or "签名密钥" in (last_error or "")
|
||||
or "web_protect" in (last_error or "")
|
||||
or last_error == "INVALID_REQUEST"
|
||||
or invalid_request
|
||||
or session_kicked
|
||||
)
|
||||
if need_browser:
|
||||
msg = last_error or "缺少 IM 签名密钥"
|
||||
if last_error == "INVALID_REQUEST":
|
||||
if session_kicked:
|
||||
msg = "抖音已踢下当前 IM 登录态(decision=KICK),请停止托管后用浏览器模式重新登录并打开私信页"
|
||||
elif invalid_request:
|
||||
msg = "IM 会话创建失败(INVALID_REQUEST),请停止托管后用浏览器模式重新登录并打开私信页"
|
||||
return SendMessageResponse(
|
||||
success=False,
|
||||
|
||||
Reference in New Issue
Block a user