"""Standard-library CDK client. Importing this module sends no requests.""" import json import time from urllib.error import HTTPError, URLError from urllib.parse import quote, urlencode from urllib.request import Request, urlopen class PublishAPIError(Exception): def __init__(self, status, data): self.status, self.data = status, data self.code = data.get("errorCode", "requestFailed") super().__init__(f"HTTP {status} ({self.code}): {data.get('error', 'Request failed')}") class PublisherClient: def __init__(self, cdk, base_url="https://chataiapi.online/fandan/api/v1", timeout=30, retries=2): self.cdk = cdk self.base_url = base_url.rstrip('/') self.timeout, self.retries = timeout, retries def request(self, method, path, data=None, query=None): url = self.base_url + path if query: url += '?' + urlencode({k: v for k, v in query.items() if v is not None}) payload = None if data is None else json.dumps(data).encode() request = Request(url, data=payload, method=method, headers={'X-Fandan-CDK': self.cdk, 'Content-Type': 'application/json', 'Accept': 'application/json'}) # Reuse the exact payload/requestId on every transport retry. for attempt in range(self.retries + 1): try: with urlopen(request, timeout=self.timeout) as response: return json.load(response) except HTTPError as exc: try: result = json.loads(exc.read()) except (ValueError, UnicodeDecodeError): result = {'error': 'Non-JSON server response'} if not isinstance(result, dict): result = {'error': 'Unexpected server response'} if exc.code not in (502, 503, 504) or attempt == self.retries: raise PublishAPIError(exc.code, result) from None except (URLError, TimeoutError, ConnectionError): if attempt == self.retries: raise time.sleep(min(2 ** attempt, 4)) def cdk_status(self): return self.request('GET', '/cdk') def takers(self, page=1): return self.request('GET', '/takers', query={'page': page}) def online_takers(self): return self.request('GET', '/online-takers') def publish(self, request_id, payment_url, access_token, target_taker_ids=None): data = {'requestId': request_id, 'paymentUrl': payment_url, 'accessToken': access_token} if target_taker_ids is not None: data['targetTakerIds'] = target_taker_ids return self.request('POST', '/orders', data) def orders(self, page=1, status=None, request_id=None): return self.request('GET', '/orders', query={'page': page, 'status': status, 'requestId': request_id}) def order(self, order_id): return self.request('GET', '/orders/' + quote(order_id, safe='')) def withdraw(self, order_id): return self.request('POST', '/orders/' + quote(order_id, safe='') + '/withdraw', {}) def delete(self, order_id): return self.request('POST', '/orders/' + quote(order_id, safe='') + '/delete', {}) def call(self, request_id, recipient_ids): return self.request('POST', '/calls', {'requestId': request_id, 'recipientIds': recipient_ids}) def call_status(self, job_id=None): return self.request('GET', '/calls' + ('/' + quote(job_id, safe='') if job_id else '')) def chat(self, after=None): return self.request('GET', '/chat', query={'after': after}) def send_chat(self, request_id, kind): return self.request('POST', '/chat', {'requestId': request_id, 'kind': kind})