|
|
|
|
@@ -9,20 +9,18 @@ logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
class AVConnectAPI:
|
|
|
|
|
_BASE_URL = "https://www.avconnect.it"
|
|
|
|
|
_LOGIN_SUCCESS_PATH = "/entraconf.php"
|
|
|
|
|
_LOGIN_DENIED_PATH = "/accessdenied.htm"
|
|
|
|
|
_SESSION_EXPIRED_PATH = "/accessespired.htm"
|
|
|
|
|
|
|
|
|
|
def __init__(self, username: str, password: str, session_id: str | None = None):
|
|
|
|
|
self._ua = UserAgent(browsers=["Chrome Mobile"], os=["Android"], platforms=["mobile"]).random
|
|
|
|
|
self._username = username
|
|
|
|
|
self._password = password
|
|
|
|
|
self._session = requests.Session()
|
|
|
|
|
self._authenticated = False
|
|
|
|
|
|
|
|
|
|
if session_id:
|
|
|
|
|
self._session.cookies.set("PHPSESSID", session_id)
|
|
|
|
|
self._authenticated = True
|
|
|
|
|
|
|
|
|
|
_LOGIN_SUCCESS_PATH = "/entraconf.php"
|
|
|
|
|
_LOGIN_DENIED_PATH = "/accessdenied.htm"
|
|
|
|
|
|
|
|
|
|
def _authenticate(self) -> bool:
|
|
|
|
|
login_url = f"{self._BASE_URL}/loginone.php"
|
|
|
|
|
@@ -35,47 +33,57 @@ class AVConnectAPI:
|
|
|
|
|
response = self._session.post(login_url, data=payload, headers=headers, allow_redirects=False)
|
|
|
|
|
location = response.headers.get("Location", "")
|
|
|
|
|
if response.status_code == 302 and self._LOGIN_SUCCESS_PATH in location:
|
|
|
|
|
self._authenticated = True
|
|
|
|
|
logger.debug("AVConnect authentication successful")
|
|
|
|
|
# Follow the redirect to complete the login and receive the PHPSESSID cookie.
|
|
|
|
|
redirect_url = location if location.startswith("http") else f"{self._BASE_URL}{location}"
|
|
|
|
|
self._session.get(redirect_url, headers={"User-Agent": self._ua})
|
|
|
|
|
logger.debug(f"AVConnect authentication successful: session_id={self._session.cookies.get('PHPSESSID')}")
|
|
|
|
|
return True
|
|
|
|
|
if self._LOGIN_DENIED_PATH in location:
|
|
|
|
|
logger.warning("AVConnect authentication denied (invalid credentials)")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning("AVConnect authentication failed: status=%d location=%r", response.status_code, location)
|
|
|
|
|
logger.warning(f"AVConnect authentication failed: status={response.status_code} location={location}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _check_sessionid(self) -> bool:
|
|
|
|
|
if not self._authenticated or not self._session.cookies.get("PHPSESSID"):
|
|
|
|
|
if not self._session.cookies.get("PHPSESSID"):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
exec_url = f"{self._BASE_URL}/exemacrocom.php"
|
|
|
|
|
headers = {
|
|
|
|
|
"User-Agent": self._ua,
|
|
|
|
|
}
|
|
|
|
|
response = self._session.get(exec_url, headers=headers)
|
|
|
|
|
logger.debug("AVConnect session check: %s", response.ok)
|
|
|
|
|
response = self._session.get(exec_url, headers=headers, allow_redirects=False)
|
|
|
|
|
if response.status_code == 302 and self._SESSION_EXPIRED_PATH in response.headers.get("Location", ""):
|
|
|
|
|
logger.debug("AVConnect session expired")
|
|
|
|
|
return False
|
|
|
|
|
logger.debug(f"AVConnect session check: {response.ok}")
|
|
|
|
|
return response.ok
|
|
|
|
|
|
|
|
|
|
def exec_gate_macro(self, id_macro) -> bool:
|
|
|
|
|
if (not self._authenticated or not self._check_sessionid()) and not self._authenticate():
|
|
|
|
|
raise Exception("Authentication failed.")
|
|
|
|
|
def exec_gate_macro(self, id_macro) -> tuple[bool, str | None]:
|
|
|
|
|
if not self._check_sessionid() and not self._authenticate():
|
|
|
|
|
raise Exception("AVConnect authentication denied (invalid credentials)")
|
|
|
|
|
exec_url = f"{self._BASE_URL}/exemacrocom.php"
|
|
|
|
|
headers = {
|
|
|
|
|
"User-Agent": self._ua,
|
|
|
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
|
|
|
|
}
|
|
|
|
|
payload = urllib.parse.urlencode({"idmacrocom": id_macro, "nome": "16"})
|
|
|
|
|
logger.debug(f"AVConnect executing gate macro: id_macro={id_macro} session_id={self._session.cookies.get('PHPSESSID')}")
|
|
|
|
|
response = self._session.post(exec_url, data=payload, headers=headers)
|
|
|
|
|
return response.ok
|
|
|
|
|
if response.ok:
|
|
|
|
|
return True, self._session.cookies.get("PHPSESSID")
|
|
|
|
|
logger.warning(f"AVConnect gate macro execution failed: id_macro={id_macro} status={response.status_code} response={response.text}")
|
|
|
|
|
return False, self._session.cookies.get("PHPSESSID")
|
|
|
|
|
|
|
|
|
|
def validate_credentials(self) -> tuple[bool, str | None]:
|
|
|
|
|
"""Attempt a login and return (ok, session_id_or_None).
|
|
|
|
|
|
|
|
|
|
def validate_credentials(username: str, password: str) -> tuple[bool, str | None]:
|
|
|
|
|
"""Attempt a login and return (ok, session_id_or_None).
|
|
|
|
|
|
|
|
|
|
Returns (False, None) if the credentials are rejected.
|
|
|
|
|
Raises on unexpected network errors.
|
|
|
|
|
"""
|
|
|
|
|
api = AVConnectAPI(username, password)
|
|
|
|
|
if not api._authenticate():
|
|
|
|
|
Returns (False, None) if the credentials are rejected.
|
|
|
|
|
Raises on unexpected network errors.
|
|
|
|
|
"""
|
|
|
|
|
logger.debug("AVConnect validating credentials by attempting login")
|
|
|
|
|
if self._authenticate():
|
|
|
|
|
logger.debug(f"AVConnect credentials valid, session_id={self._session.cookies.get('PHPSESSID')}")
|
|
|
|
|
return True, self._session.cookies.get("PHPSESSID")
|
|
|
|
|
logger.debug("AVConnect credentials invalid")
|
|
|
|
|
return False, None
|
|
|
|
|
session_id = api._session.cookies.get("PHPSESSID") or None
|
|
|
|
|
return True, session_id
|