w
This commit is contained in:
+20
-24
@@ -10,14 +10,15 @@ mirroring what the OWA web frontend sends.
|
||||
|
||||
import json
|
||||
import time
|
||||
from urllib.parse import quote, urlparse
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote, urlparse
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class SessionExpiredError(Exception):
|
||||
"""Raised when the OWA session has expired (HTTP 401/440 or HTML redirect)."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -53,14 +54,11 @@ class OWAClient:
|
||||
owa_url: str | None = None,
|
||||
):
|
||||
self.cookie_file = Path(
|
||||
cookie_file
|
||||
or str(Path(__file__).parent.parent / "session-cookies.txt")
|
||||
cookie_file or str(Path(__file__).parent.parent / "session-cookies.txt")
|
||||
)
|
||||
self.owa_url = (owa_url or "").rstrip("/")
|
||||
if not self.owa_url:
|
||||
raise ValueError(
|
||||
"OWA URL must be provided via --owa-url or owa_url=…"
|
||||
)
|
||||
raise ValueError("OWA URL must be provided via --owa-url or owa_url=…")
|
||||
self._session = requests.Session()
|
||||
self._loaded = False
|
||||
self.user_email: str = ""
|
||||
@@ -81,9 +79,10 @@ class OWAClient:
|
||||
"""
|
||||
now_ms = int(time.time() * 1000)
|
||||
client_req_id = f"{self._client_id}_{now_ms}"
|
||||
now_str = time.strftime(
|
||||
"%Y-%m-%dT%H:%M:%S.", time.gmtime(now_ms / 1000)
|
||||
) + f"{now_ms % 1000:03d}"
|
||||
now_str = (
|
||||
time.strftime("%Y-%m-%dT%H:%M:%S.", time.gmtime(now_ms / 1000))
|
||||
+ f"{now_ms % 1000:03d}"
|
||||
)
|
||||
return {
|
||||
# Basic
|
||||
"accept": "*/*",
|
||||
@@ -129,9 +128,7 @@ class OWAClient:
|
||||
@property
|
||||
def _client_id(self) -> str:
|
||||
"""Extract ClientId from session cookies."""
|
||||
cid = next(
|
||||
(c.value for c in self._session.cookies if c.name == "ClientId"), ""
|
||||
)
|
||||
cid = next((c.value for c in self._session.cookies if c.name == "ClientId"), "")
|
||||
return cid if cid else "00000000000000000000000000000000"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -153,7 +150,9 @@ class OWAClient:
|
||||
cookies[name] = value
|
||||
|
||||
if not cookies:
|
||||
raise SessionExpiredError("Cookie file is empty. Use the login MCP tool to authenticate.")
|
||||
raise SessionExpiredError(
|
||||
"Cookie file is empty. Use the login MCP tool to authenticate."
|
||||
)
|
||||
|
||||
self._session = requests.Session()
|
||||
self._session.cookies.update(cookies)
|
||||
@@ -229,12 +228,11 @@ class OWAClient:
|
||||
set_cookie = resp.headers.get("Set-Cookie", "")
|
||||
if "X-OWA-CANARY=" in set_cookie:
|
||||
import re
|
||||
m = re.search(r'X-OWA-CANARY=([^;]+)', set_cookie)
|
||||
|
||||
m = re.search(r"X-OWA-CANARY=([^;]+)", set_cookie)
|
||||
if m:
|
||||
host = urlparse(self.owa_url).hostname or "owa.b1.ru"
|
||||
self._session.cookies.set(
|
||||
"X-OWA-CANARY", m.group(1), domain=host
|
||||
)
|
||||
self._session.cookies.set("X-OWA-CANARY", m.group(1), domain=host)
|
||||
# Persist any cookie changes
|
||||
self._save_cookies()
|
||||
except Exception:
|
||||
@@ -273,7 +271,9 @@ class OWAClient:
|
||||
|
||||
# Detect session expiry
|
||||
if resp.status_code in (401, 440):
|
||||
raise SessionExpiredError("Session expired (HTTP {}).".format(resp.status_code))
|
||||
raise SessionExpiredError(
|
||||
f"Session expired (HTTP {resp.status_code})."
|
||||
)
|
||||
|
||||
if "text/html" in resp.headers.get("Content-Type", ""):
|
||||
body_snippet = resp.text[:300] if resp.text else ""
|
||||
@@ -335,9 +335,7 @@ class OWAClient:
|
||||
raise SessionExpiredError(f"Download failed: {exc}") from exc
|
||||
|
||||
if resp.status_code in (401, 440):
|
||||
raise SessionExpiredError(
|
||||
f"Session expired (HTTP {resp.status_code})."
|
||||
)
|
||||
raise SessionExpiredError(f"Session expired (HTTP {resp.status_code}).")
|
||||
|
||||
if "text/html" in resp.headers.get("Content-Type", ""):
|
||||
raise SessionExpiredError(
|
||||
@@ -469,9 +467,7 @@ class OWAClient:
|
||||
# ResolveNames (directory search / attendee resolution)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def resolve_names(
|
||||
self, query: str, *, full_contact: bool = True
|
||||
) -> list[dict]:
|
||||
def resolve_names(self, query: str, *, full_contact: bool = True) -> list[dict]:
|
||||
"""Call ResolveNames to search the directory.
|
||||
|
||||
Returns the list of Resolution dicts from the API, each containing
|
||||
|
||||
Reference in New Issue
Block a user