feat: 实现清理失效账号安全方案(包含预览、SSE测试、安全删除以及本地标记清空)
This commit is contained in:
@@ -345,7 +345,7 @@ class Sub2ApiWebsiteClient:
|
||||
|
||||
def account_exists(self, account_id: str, endpoint: str = "/accounts") -> bool | None:
|
||||
"""检查目标账号是否存在。
|
||||
|
||||
|
||||
优先拉取账号列表判断:
|
||||
- 列表成功取到 → return account_id in ids(True=存在,False=已删除)
|
||||
- 列表取不到(None)→ return None(校验失败,不清本地)
|
||||
@@ -357,6 +357,82 @@ class Sub2ApiWebsiteClient:
|
||||
return None
|
||||
return account_id in ids
|
||||
|
||||
def test_account(self, account_id: str, endpoint: str = "/accounts") -> dict[str, Any]:
|
||||
"""测试远端账号可用性。利用 SSE 监听 test_complete 事件的 success 状态。
|
||||
若 404,返回 {"status": "404", "cleanup_allowed": True, "message": "账号不存在"}
|
||||
若 timeout / 无法建立连接,返回 {"status": "timeout"/"error", "cleanup_allowed": False}
|
||||
"""
|
||||
import json
|
||||
url = self._url(f"{endpoint}/{account_id}/test")
|
||||
headers = self._headers()
|
||||
# Accept text/event-stream since it's an SSE stream
|
||||
headers["Accept"] = "text/event-stream"
|
||||
|
||||
try:
|
||||
with self._client.stream("POST", url, headers=headers) as response:
|
||||
if response.status_code == 404:
|
||||
return {"status": "404", "cleanup_allowed": True, "message": "账号在远端不存在 (404)"}
|
||||
if response.status_code >= 400:
|
||||
return {"status": "http_error", "cleanup_allowed": False, "message": f"测试接口返回 HTTP {response.status_code}"}
|
||||
|
||||
# Read SSE stream line by line
|
||||
current_event = None
|
||||
success_value = None
|
||||
has_error_event = False
|
||||
|
||||
for line in response.iter_lines():
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith("event:"):
|
||||
current_event = line[6:].strip()
|
||||
elif line.startswith("data:"):
|
||||
data_str = line[5:].strip()
|
||||
if current_event == "error":
|
||||
has_error_event = True
|
||||
if current_event == "test_complete" or not current_event:
|
||||
try:
|
||||
parsed = json.loads(data_str)
|
||||
if isinstance(parsed, dict):
|
||||
if "success" in parsed:
|
||||
success_value = parsed["success"]
|
||||
elif parsed.get("event") == "test_complete" and "success" in parsed:
|
||||
success_value = parsed["success"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if success_value is True:
|
||||
return {"status": "success", "cleanup_allowed": False, "message": "测试成功,账号有效"}
|
||||
elif success_value is False:
|
||||
return {"status": "failed", "cleanup_allowed": True, "message": "测试失败,账号失效"}
|
||||
elif has_error_event:
|
||||
return {"status": "error", "cleanup_allowed": False, "message": "测试接口返回了错误事件"}
|
||||
else:
|
||||
return {"status": "invalid_sse", "cleanup_allowed": False, "message": "未收到有效的测试完成事件"}
|
||||
|
||||
except httpx.TimeoutException as exc:
|
||||
return {"status": "timeout", "cleanup_allowed": False, "message": f"连接超时: {exc}"}
|
||||
except Exception as exc:
|
||||
return {"status": "error", "cleanup_allowed": False, "message": f"请求异常: {exc}"}
|
||||
|
||||
def delete_account(self, account_id: str, endpoint: str = "/accounts") -> dict[str, Any]:
|
||||
"""删除目标账号。
|
||||
若 404 视为 already_deleted。
|
||||
"""
|
||||
url = self._url(f"{endpoint}/{account_id}")
|
||||
headers = self._headers()
|
||||
try:
|
||||
resp = self._client.request("DELETE", url, headers=headers)
|
||||
if resp.status_code == 404:
|
||||
return {"status": "already_deleted", "message": "账号不存在或已在远端被删除"}
|
||||
resp.raise_for_status()
|
||||
return {"status": "deleted", "message": "账号已删除"}
|
||||
except httpx.HTTPStatusError as exc:
|
||||
if exc.response.status_code == 404:
|
||||
return {"status": "already_deleted", "message": "账号不存在或已在远端被删除"}
|
||||
raise WebsiteError(_friendly_http_error(exc)) from exc
|
||||
except Exception as exc:
|
||||
raise WebsiteError(f"删除账号失败: {exc}") from exc
|
||||
|
||||
@staticmethod
|
||||
def extract_id(value: Any) -> str:
|
||||
return _extract_id(value)
|
||||
|
||||
Reference in New Issue
Block a user