ForcePilot/backend/package/yuxi/channel/extensions/twitter/errors.py

86 lines
2.6 KiB
Python
Raw Normal View History

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