ForcePilot/backend/package/yuxi/channel/extensions/ringcentral/sdk.py

129 lines
4.3 KiB
Python
Raw Normal View History

from __future__ import annotations
import asyncio
import logging
from ringcentral import SDK
from ringcentral.http.api_exception import ApiException
from yuxi.channel.extensions.ringcentral.errors import (
RingCentralAuthError,
RingCentralError,
RingCentralNotFoundError,
RingCentralPermissionError,
RingCentralRateLimitError,
RingCentralValidationError,
)
logger = logging.getLogger(__name__)
class AsyncRingCentralClient:
def __init__(self, client_id: str, client_secret: str, server_url: str):
self._sdk = SDK(client_id, client_secret, server_url)
self._platform = self._sdk.platform()
self.client_id = client_id
self.server_url = server_url
async def login_jwt(self, jwt_token: str) -> dict:
def _login():
return self._platform.login(jwt=jwt_token)
try:
resp = await asyncio.to_thread(_login)
data = resp.json()
logger.info("RingCentral JWT login succeeded, owner_id=%s", data.get("owner_id", ""))
return data
except ApiException as e:
raise RingCentralAuthError(getattr(e, "status_code", 401), str(e)) from e
async def ensure_authenticated(self, jwt_token: str) -> None:
auth = self._platform.auth()
if auth and auth.access_token_valid():
return
logger.info("RingCentral access token expired, re-authenticating...")
await self.login_jwt(jwt_token)
@property
def is_logged_in(self) -> bool:
return self._platform.logged_in()
@property
def platform(self):
return self._platform
async def _call(
self,
method: str,
path: str,
body: dict | None = None,
params: dict | None = None,
) -> dict:
def _do():
if method == "GET":
return self._platform.get(path, params or {})
elif method == "POST":
return self._platform.post(path, body)
elif method == "PATCH":
return self._platform.patch(path, body)
elif method == "PUT":
return self._platform.put(path, body)
elif method == "DELETE":
return self._platform.delete(path)
raise ValueError(f"Unknown method: {method}")
try:
resp = await asyncio.to_thread(_do)
if resp.ok():
return resp.json()
raise self._parse_error(resp)
except ApiException as e:
raise self._parse_api_exception(e)
def _parse_error(self, response) -> RingCentralError:
status = response.status
try:
body = response.json()
msg = body.get("message", response.text)
except Exception:
msg = response.text
if status == 429:
retry_after = response.headers.get("Retry-After", "60")
return RingCentralRateLimitError(status, msg, retry_after=int(retry_after))
if status == 404:
return RingCentralNotFoundError(status, msg)
if status in (400, 422):
return RingCentralValidationError(status, msg)
if status == 403:
return RingCentralPermissionError(status, msg)
if status in (401,):
return RingCentralAuthError(status, msg)
return RingCentralError(status, msg)
def _parse_api_exception(self, e: ApiException) -> RingCentralError:
msg = str(e)
if "rate limit" in msg.lower() or "429" in msg:
return RingCentralRateLimitError(429, msg, retry_after=60)
if "unauthorized" in msg.lower() or "token" in msg.lower():
return RingCentralAuthError(401, msg)
return RingCentralError(500, msg)
async def get(self, path: str, params: dict | None = None) -> dict:
return await self._call("GET", path, params=params)
async def post(self, path: str, body: dict | None = None) -> dict:
return await self._call("POST", path, body=body)
async def patch(self, path: str, body: dict | None = None) -> dict:
return await self._call("PATCH", path, body=body)
async def put(self, path: str, body: dict | None = None) -> dict:
return await self._call("PUT", path, body=body)
async def delete(self, path: str) -> dict:
return await self._call("DELETE", path)
async def close(self) -> None:
pass