""" Client API Yeastar P520 Authentification et accès à l'API REST du P520 """ import requests import hashlib import base64 import time import uuid from typing import Optional class YeastarClient: """Client pour l'API Yeastar P-Series (P520)""" def __init__(self, base_url: str, username: str, password: str): self.base_url = base_url.rstrip('/') self.username = username self.password = password self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=UTF-8', 'Origin': self.base_url, 'Referer': f'{self.base_url}/login' }) self._logged_in = False self._device_mark = None def _generate_device_mark(self) -> str: """Génère un identifiant de device unique""" timestamp = int(time.time() * 1000) random_str = uuid.uuid4().hex[:28] return f"{timestamp}-{random_str}" def _encode_password(self, password: str) -> str: """Encode le mot de passe: MD5 → Base64""" md5_hash = hashlib.md5(password.encode()).hexdigest() return base64.b64encode(md5_hash.encode()).decode() def login(self, language: str = "fr") -> dict: """Authentification auprès du P520""" self._device_mark = self._generate_device_mark() # Préparer les paramètres de l'URL params = { "username": self.username, "language": language, "supportcrx": "true", "linkus_devicemark": self._device_mark, "login_link_type": "all" } # Préparer le body body = { "username": self.username, "password": self._encode_password(self.password), "language": language, "supportcrx": True, "linkus_devicemark": self._device_mark, "login_link_type": "all" } # Requête de login url = f"{self.base_url}/api/v1.0/login" resp = self.session.post(url, params=params, json=body, verify=False, timeout=10) resp.raise_for_status() data = resp.json() if data.get('errcode') == 0: self._logged_in = True return {"success": True, "data": data} else: return {"success": False, "error": data.get('errmsg', 'Unknown error')} def get_extensions(self) -> list: """Récupère la liste des extensions""" if not self._logged_in: raise RuntimeError("Non connecté. Appeler login() d'abord.") url = f"{self.base_url}/api/v1.0/extension/searchwebclientext" resp = self.session.get(url, verify=False, timeout=10) resp.raise_for_status() data = resp.json() if data.get('errcode') == 0: return data.get('ext_list', []) return [] def get_personal_info(self) -> dict: """Récupère les infos de l'extension connectée""" if not self._logged_in: raise RuntimeError("Non connecté. Appeler login() d'abord.") url = f"{self.base_url}/api/v1.0/extension/getpersonal" resp = self.session.get(url, verify=False, timeout=10) resp.raise_for_status() data = resp.json() if data.get('errcode') == 0: return data.get('extension', {}) return {} def get_pbx_info(self) -> dict: """Récupère les informations du PBX""" if not self._logged_in: raise RuntimeError("Non connecté. Appeler login() d'abord.") url = f"{self.base_url}/api/v1.0/pbx/getinformation" resp = self.session.get(url, verify=False, timeout=10) resp.raise_for_status() data = resp.json() if data.get('errcode') == 0: return data.get('information', {}) return {} def set_presence(self, status: str, extension_id: int = None) -> bool: """Change le statut de présence d'une extension Args: status: available, away, business_trip, do_not_disturb, launch, off_work extension_id: ID de l'extension (défaut: extension connectée) Returns: True si succès """ if not self._logged_in: raise RuntimeError("Non connecté. Appeler login() d'abord.") valid_statuses = ['available', 'away', 'business_trip', 'do_not_disturb', 'launch', 'off_work'] if status not in valid_statuses: raise ValueError(f"Statut invalide. Valeurs: {valid_statuses}") # Si pas d'ID fourni, utiliser l'extension connectée if extension_id is None: personal = self.get_personal_info() extension_id = personal.get('id') if not extension_id: raise RuntimeError("Impossible de déterminer l'ID de l'extension") url = f"{self.base_url}/api/v1.0/extension/update" body = { "id": extension_id, "presence_status": status } resp = self.session.post(url, json=body, verify=False, timeout=10) resp.raise_for_status() data = resp.json() return data.get('errcode') == 0 def logout(self): """Déconnexion""" if self._logged_in: url = f"{self.base_url}/api/v1.0/logout" try: self.session.post(url, verify=False, timeout=5) except: pass self.session.close() self._logged_in = False def __enter__(self): self.login() return self def __exit__(self, exc_type, exc_val, exc_tb): self.logout()