|
@@ -11,7 +11,7 @@ from urllib.parse import parse_qs, urlparse
|
|
|
|
|
|
|
|
import httpx
|
|
import httpx
|
|
|
|
|
|
|
|
-from ._scripts import refresh_booking_cookies
|
|
|
|
|
|
|
+from ._scripts import _assert_not_async, _booking_lock, _npm_env, _ts_dir, _ensure_installed
|
|
|
from room_cal._utils import data_home, get_config, read_env
|
|
from room_cal._utils import data_home, get_config, read_env
|
|
|
from ._utils import month_bounds, months_in_range
|
|
from ._utils import month_bounds, months_in_range
|
|
|
from .credentials import is_stale, mark_stale, set_credentials
|
|
from .credentials import is_stale, mark_stale, set_credentials
|
|
@@ -252,6 +252,21 @@ class _BookingClient:
|
|
|
except Exception:
|
|
except Exception:
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def refresh_credentials() -> None:
|
|
|
|
|
+ _assert_not_async('refresh_credentials')
|
|
|
|
|
+ import subprocess
|
|
|
|
|
+ ts_dir = _ts_dir()
|
|
|
|
|
+ _ensure_installed(ts_dir)
|
|
|
|
|
+ _log.info('booking: starting credential refresh')
|
|
|
|
|
+ with _booking_lock:
|
|
|
|
|
+ try:
|
|
|
|
|
+ subprocess.run(['npm', 'run', 'refresh-booking'], cwd=ts_dir, env=_npm_env(), check=True)
|
|
|
|
|
+ except subprocess.CalledProcessError as e:
|
|
|
|
|
+ _log.error('booking: credential refresh failed (exit %d)', e.returncode)
|
|
|
|
|
+ raise ServiceAuthError(f'Booking.com credential refresh failed (exit {e.returncode})') from None
|
|
|
|
|
+ _log.info('booking: credential refresh complete')
|
|
|
|
|
+
|
|
|
async def _auto_refresh(self, *, trigger: str = 'unknown') -> None:
|
|
async def _auto_refresh(self, *, trigger: str = 'unknown') -> None:
|
|
|
_log.warning('Booking.com session may be expired — trying HTTP session refresh')
|
|
_log.warning('Booking.com session may be expired — trying HTTP session refresh')
|
|
|
if await self._try_http_session_refresh():
|
|
if await self._try_http_session_refresh():
|
|
@@ -259,7 +274,7 @@ class _BookingClient:
|
|
|
return
|
|
return
|
|
|
_log.warning('HTTP refresh failed — launching headless Playwright')
|
|
_log.warning('HTTP refresh failed — launching headless Playwright')
|
|
|
try:
|
|
try:
|
|
|
- await asyncio.get_running_loop().run_in_executor(None, refresh_booking_cookies)
|
|
|
|
|
|
|
+ await asyncio.get_running_loop().run_in_executor(None, _BookingClient.refresh_credentials)
|
|
|
self._reload_from_env()
|
|
self._reload_from_env()
|
|
|
except ServiceAuthError:
|
|
except ServiceAuthError:
|
|
|
mark_stale('booking')
|
|
mark_stale('booking')
|
|
@@ -659,7 +674,7 @@ class _BookingClient:
|
|
|
# GraphQL ISE on send typically means the messaging 2FA session has expired.
|
|
# GraphQL ISE on send typically means the messaging 2FA session has expired.
|
|
|
# Re-trigger the SMS challenge and retry once.
|
|
# Re-trigger the SMS challenge and retry once.
|
|
|
_log.warning('booking send_message ISE on attempt 1 res=%s idempotency=%s — re-triggering credential refresh', reservation_id, idempotency)
|
|
_log.warning('booking send_message ISE on attempt 1 res=%s idempotency=%s — re-triggering credential refresh', reservation_id, idempotency)
|
|
|
- await asyncio.get_running_loop().run_in_executor(None, refresh_booking_cookies)
|
|
|
|
|
|
|
+ await asyncio.get_running_loop().run_in_executor(None, _BookingClient.refresh_credentials)
|
|
|
self._reload_from_env()
|
|
self._reload_from_env()
|
|
|
raw = await self._msg_gql('postFreeTextMessage', _POST_FREE_TEXT_MUTATION, payload, attempt=2)
|
|
raw = await self._msg_gql('postFreeTextMessage', _POST_FREE_TEXT_MUTATION, payload, attempt=2)
|
|
|
msg = ((raw.get('data') or {}).get('chatMessage') or {}).get('postFreeTextMessage') or {}
|
|
msg = ((raw.get('data') or {}).get('chatMessage') or {}).get('postFreeTextMessage') or {}
|