"""2600Hz Kazoo preset — REST API pull. Kazoo exposes ``/v2/accounts/{account_id}/cdrs`` with bearer-token auth. We page through and materialize as NDJSON; the generic_csv adapter with a Kazoo-specific column map reads it back. """ from __future__ import annotations import json import logging import urllib.request from datetime import datetime, timedelta from typing import Iterable, Optional from .base import BasePreset, CredentialField, FetchedFile logger = logging.getLogger(__name__) class KazooPreset(BasePreset): PRESET_SLUG = "kazoo" LABEL = "2600Hz Kazoo" CDR_FORMAT = "generic_csv" TRANSPORT_METHOD = "api" DEFAULT_CRON = "0 2 * * *" CREDENTIAL_FIELDS = ( CredentialField("api_host", "Kazoo API host", "text", help="e.g. https://api.example.com:8443"), CredentialField("account_id", "Kazoo account ID", "text"), CredentialField("auth_token", "Kazoo auth token", "password", sensitive=True, help="Generated via the Kazoo user-auth API."), ) FORMAT_CONFIG = { "start_time": "timestamp", "caller_number": "caller_id_number", "called_number": "callee_id_number", "duration_sec": "duration_seconds", "billed_amount": "billing_seconds", # Kazoo doesn't bill here by default "call_id": "call_id", "trunk_group": "request", } def _request(self, url: str, token: str) -> bytes: req = urllib.request.Request( url, headers={"X-Auth-Token": token, "Accept": "application/json"}, ) with urllib.request.urlopen(req, timeout=30) as resp: return resp.read() def validate(self, profile_config: dict, secrets: dict) -> tuple[bool, str]: try: base = profile_config["api_host"].rstrip("/") acct = profile_config["account_id"] self._request( f"{base}/v2/accounts/{acct}", token=secrets["auth_token"], ) return True, "Account endpoint reachable" except Exception as exc: return False, f"Kazoo validate failed: {exc}" def fetch( self, profile_config: dict, secrets: dict, since: Optional[datetime], ) -> Iterable[FetchedFile]: base = profile_config["api_host"].rstrip("/") acct = profile_config["account_id"] token = secrets["auth_token"] since = since or (datetime.utcnow() - timedelta(days=1)) end = datetime.utcnow() start_epoch = int(since.timestamp()) + 62167219200 # Kazoo uses gregorian epoch end_epoch = int(end.timestamp()) + 62167219200 records: list[dict] = [] page_key: Optional[str] = None while True: qs = f"created_from={start_epoch}&created_to={end_epoch}&paginate=true&page_size=500" if page_key: qs += f"&start_key={page_key}" body = self._request(f"{base}/v2/accounts/{acct}/cdrs?{qs}", token) payload = json.loads(body) items = payload.get("data", []) records.extend(items) page_key = payload.get("next_start_key") if not page_key or not items: break if not records: return ndjson = "\n".join(json.dumps(r) for r in records).encode("utf-8") yield FetchedFile( remote_path=f"kazoo_cdrs_{end:%Y%m%dT%H%M%SZ}.ndjson", mtime=end, content=ndjson, size_bytes=len(ndjson), )