171 lines
No EOL
5.8 KiB
Python
171 lines
No EOL
5.8 KiB
Python
"""
|
|
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() |