ForcePilot/backend/package/yuxi/channel/extensions/twitter/errors.py
Kris 1c590097be feat(channel): 添加 Twitter 和 Viber 渠道扩展
新增 Twitter 和 Viber 两个渠道扩展。

Twitter 渠道扩展功能模块:
- auth: OAuth 认证管理
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- tweets: 推文管理
- social: 社交互动
- reactions: 表情反应
- media: 媒体资源处理

Viber 渠道扩展功能模块:
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- rate_limiter: 速率限制
- media: 媒体资源处理
2026-05-21 11:57:22 +08:00

86 lines
2.6 KiB
Python

from __future__ import annotations
from enum import StrEnum
class TwitterErrorKind(StrEnum):
RETRYABLE = "retryable"
RATE_LIMITED = "rate_limited"
AUTH = "auth"
NOT_AUTHORIZED_DM = "not_authorized_dm"
DUPLICATE = "duplicate"
MESSAGE_TOO_LONG = "message_too_long"
MEDIA_TOO_LARGE = "media_too_large"
MEDIA_INVALID = "media_invalid"
ACCOUNT_LOCKED = "account_locked"
FORBIDDEN = "forbidden"
NOT_FOUND = "not_found"
FATAL = "fatal"
NETWORK = "network"
def classify_error(
status_code: int | None,
error_body: dict | str | None,
) -> tuple[TwitterErrorKind, str, float | None]:
description = ""
if isinstance(error_body, dict):
description = error_body.get("detail", error_body.get("title", ""))
errors_list = error_body.get("errors", [])
if errors_list:
description = errors_list[0].get("message", description)
elif isinstance(error_body, str):
description = error_body
retry_after = None
if isinstance(error_body, dict):
retry_after = error_body.get("retry_after")
if status_code == 429:
return (
TwitterErrorKind.RATE_LIMITED,
description,
float(retry_after) if retry_after else 180.0,
)
if status_code == 401:
return TwitterErrorKind.AUTH, description, None
if status_code == 403:
if "not authorized to send" in description.lower():
return TwitterErrorKind.NOT_AUTHORIZED_DM, description, None
if "duplicate" in description.lower():
return TwitterErrorKind.DUPLICATE, description, None
return TwitterErrorKind.FORBIDDEN, description, None
if status_code == 400:
if "too long" in description.lower() or "length" in description.lower():
return TwitterErrorKind.MESSAGE_TOO_LONG, description, None
if "media" in description.lower():
return TwitterErrorKind.MEDIA_INVALID, description, None
if "duplicate" in description.lower():
return TwitterErrorKind.DUPLICATE, description, None
return TwitterErrorKind.FATAL, description, None
if status_code == 404:
return TwitterErrorKind.NOT_FOUND, description, None
if status_code and status_code >= 500:
return TwitterErrorKind.RETRYABLE, description, None
if status_code is None:
return TwitterErrorKind.NETWORK, description, None
return TwitterErrorKind.FATAL, description, None
def is_retryable(kind: TwitterErrorKind) -> bool:
return kind in (
TwitterErrorKind.RETRYABLE,
TwitterErrorKind.RATE_LIMITED,
TwitterErrorKind.NETWORK,
)
MAX_RETRIES = 3