diff --git a/bot/services/mikopbx_service.py b/bot/services/mikopbx_service.py index a70e61b..8073375 100644 --- a/bot/services/mikopbx_service.py +++ b/bot/services/mikopbx_service.py @@ -27,9 +27,8 @@ class MikoPBXService: def _get_auth_headers(self) -> Dict[str, str]: headers = { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", - "Accept": "application/json", - "X-Requested-With": "XMLHttpRequest" + "Content-Type": "application/json", + "Accept": "application/json" } if self.api_token: headers["Authorization"] = f"Bearer {self.api_token}" @@ -42,7 +41,7 @@ class MikoPBXService: logger.info(">>> Using Basic Auth (fallback)") return headers - async def _make_request(self, method: str, endpoint: str, data: Optional[Dict] = None, params: Optional[Dict] = None): + async def _make_request(self, method: str, endpoint: str, json_data: Optional[Dict] = None, params: Optional[Dict] = None): session = await self._get_session() url = f"{self.host}{endpoint}" headers = self._get_auth_headers() @@ -51,22 +50,30 @@ class MikoPBXService: logger.info(f">>> REQUEST: {method.upper()} {url}") if params: logger.info(f">>> PARAMS: {params}") - if data: - logger.info(f">>> BODY: {data}") + if json_data: + logger.info(f">>> BODY:\n{json.dumps(json_data, indent=2, ensure_ascii=False)}") try: if method.upper() == "GET": async with session.get(url, params=params, headers=headers) as resp: text = await resp.text() - logger.info(f"<<< RESPONSE [{resp.status}]:\n{text[:2500]}") + logger.info(f"<<< RESPONSE [{resp.status}]:\n{text[:3000]}") + try: + return await resp.json() + except: + return {"result": False, "raw": text} + elif method.upper() == "DELETE": + async with session.delete(url, headers=headers) as resp: + text = await resp.text() + logger.info(f"<<< RESPONSE [{resp.status}]:\n{text[:3000]}") try: return await resp.json() except: return {"result": False, "raw": text} else: - async with session.post(url, data=data, headers=headers) as resp: + async with session.post(url, json=json_data, headers=headers) as resp: text = await resp.text() - logger.info(f"<<< RESPONSE [{resp.status}]:\n{text[:2500]}") + logger.info(f"<<< RESPONSE [{resp.status}]:\n{text[:3000]}") try: return await resp.json() except: @@ -75,59 +82,47 @@ class MikoPBXService: logger.error(f"!!! REQUEST ERROR: {e}") return {"result": False, "messages": [str(e)]} - # ==================== EXTENSIONS (legacy but working) ==================== + # ==================== EMPLOYEES (correct v3 endpoint) ==================== - async def get_extension_template(self) -> Optional[Dict]: - """Получаем шаблон через старый, но рабочий эндпоинт""" - logger.info(">>> Getting extension template via legacy endpoint...") - response = await self._make_request( - "GET", - "/pbxcore/api/extensions/getRecord", - params={"id": ""} - ) - if response.get("result"): - logger.info(">>> Template received successfully") - return response.get("data") - logger.error(f">>> Failed to get template: {response.get('messages')}") - return None + async def create_extension(self, number: str, secret: str, username: str = "", email: str = "") -> Dict[str, Any]: + """ + Create new employee (SIP extension) using the correct endpoint + POST /pbxcore/api/v3/employees + """ + logger.info(f"\n>>> Creating employee/extension {number} via /v3/employees ...") - async def create_extension(self, number: str, secret: str = None, username: str = "", email: str = "") -> Dict[str, Any]: - logger.info(f"\n>>> Creating extension {number}...") + payload = { + "number": number, + "user_username": username or f"User {number}", + "sip_secret": secret, + } - template = await self.get_extension_template() - if not template: - return {"success": False, "error": "Failed to get template from MikoPBX"} - - # Заполняем шаблон - template["number"] = number - if username: - template["user_username"] = username if email: - template["user_email"] = email - template["type"] = "SIP" - template["show_in_phonebook"] = "1" - template["is_general_user_number"] = "1" + payload["user_email"] = email - if secret: - template["sip_secret"] = secret + # Optional sensible defaults + payload["sip_enableRecording"] = True + payload["sip_dtmfmode"] = "auto" + payload["sip_transport"] = "udp,tcp" + payload["fwd_ringlength"] = 45 - logger.info(f">>> Sending saveRecord request...") + logger.info(f">>> Sending create employee request...") response = await self._make_request( "POST", - "/pbxcore/api/extensions/saveRecord", - data=template + "/pbxcore/api/v3/employees", + json_data=payload ) if response.get("result"): - logger.info(">>> Extension created successfully!") + logger.info(">>> SUCCESS: Employee created!") return { "success": True, "number": number, "data": response.get("data", {}) } else: - error = response.get("messages", ["Unknown error"]) + error = response.get("messages") or response.get("error") or ["Unknown error"] logger.error(f">>> Creation failed: {error}") return { "success": False, @@ -135,11 +130,8 @@ class MikoPBXService: } async def get_extension(self, number: str) -> Optional[Dict]: - response = await self._make_request( - "GET", - "/pbxcore/api/extensions/getRecord", - params={"id": number} - ) + """Get employee by number""" + response = await self._make_request("GET", f"/pbxcore/api/v3/employees/{number}") return response.get("data") if response.get("result") else None async def set_extension_secret(self, number: str, new_secret: str) -> Dict[str, Any]: @@ -147,23 +139,11 @@ class MikoPBXService: if not current: return {"success": False, "error": "Extension not found"} current["sip_secret"] = new_secret - response = await self._make_request( - "POST", - "/pbxcore/api/extensions/saveRecord", - data=current - ) + response = await self._make_request("PUT", f"/pbxcore/api/v3/employees/{number}", json_data=current) return {"success": response.get("result", False), "messages": response.get("messages", [])} async def delete_extension(self, number: str) -> Dict[str, Any]: - current = await self.get_extension(number) - if not current: - return {"success": False, "error": "Extension not found"} - record_id = current.get("id") or number - response = await self._make_request( - "POST", - "/pbxcore/api/extensions/deleteRecord", - data={"id": record_id} - ) + response = await self._make_request("DELETE", f"/pbxcore/api/v3/employees/{number}") return {"success": response.get("result", False), "messages": response.get("messages", [])} async def close(self):