Pārlūkot izejas kodu

Wire 2FA re-trigger into send_message; split CLI vs library entry point

ZiB 4 mēneši atpakaļ
vecāks
revīzija
a8161b4a42
3 mainītis faili ar 35 papildinājumiem un 16 dzēšanām
  1. 1 1
      pyproject.toml
  2. 23 15
      room_cal/service_api/_booking.py
  3. 11 0
      room_cal/service_api/_scripts.py

+ 1 - 1
pyproject.toml

@@ -15,7 +15,7 @@ dependencies = [
 
 [project.scripts]
 refresh-creds = "room_cal.service_api._scripts:refresh_cookies"
-booking-2fa = "room_cal.service_api._scripts:trigger_booking_chat_auth"
+booking-2fa = "room_cal.service_api._scripts:_trigger_booking_chat_auth_cli"
 room-cal = "room_cal.actions._cli:main"
 room-api = "room_cal.api._server:main"
 room-mcp = "room_cal.mcp._server:main"

+ 23 - 15
room_cal/service_api/_booking.py

@@ -11,7 +11,7 @@ from urllib.parse import parse_qs, urlparse
 
 import httpx
 
-from ._scripts import refresh_booking_cookies
+from ._scripts import refresh_booking_cookies, trigger_booking_chat_auth
 from room_cal._utils import data_home, get_config, read_env
 from ._utils import month_bounds, months_in_range
 from .credentials import is_stale, mark_stale, set_credentials
@@ -601,20 +601,28 @@ class _BookingClient:
     if is_stale('booking'):
       raise BookingAuthError(f'Booking.com credentials are stale — run: {_AUTH_HINT}')
     idempotency = f'{reservation_id}-{int(time.time() * 1000)}'
-    await self._msg_gql(
-      'postFreeTextMessage', _POST_FREE_TEXT_MUTATION,
-      {'input': {
-        'actor': self._actor(),
-        'conversationReference': {
-          'productId': reservation_id,
-          'productType': 'POST_BOOKING',
-          'verticalType': 'ACCOMMODATION',
-        },
-        'idempotency': idempotency,
-        'content': body,
-        'attachmentIds': [],
-      }},
-    )
+    payload = {'input': {
+      'actor': self._actor(),
+      'conversationReference': {
+        'productId': reservation_id,
+        'productType': 'POST_BOOKING',
+        'verticalType': 'ACCOMMODATION',
+      },
+      'idempotency': idempotency,
+      'content': body,
+      'attachmentIds': [],
+    }}
+    try:
+      await self._msg_gql('postFreeTextMessage', _POST_FREE_TEXT_MUTATION, payload)
+    except RuntimeError as e:
+      if 'Internal Server Error' not in str(e):
+        raise
+      # GraphQL ISE on send typically means the messaging 2FA session has expired.
+      # Re-trigger the SMS challenge and retry once.
+      _log.warning('Booking.com send_message failed with ISE — re-triggering 2FA')
+      trigger_booking_chat_auth()
+      self._reload_from_env()
+      await self._msg_gql('postFreeTextMessage', _POST_FREE_TEXT_MUTATION, payload)
 
   async def set_price(self, d: str, price: int, occupancy: int = 2) -> None:
     if is_stale('booking'):

+ 11 - 0
room_cal/service_api/_scripts.py

@@ -58,6 +58,17 @@ def refresh_airbnb_cookies() -> None:
 
 
 def trigger_booking_chat_auth() -> None:
+  """Run booking-2fa; raises ServiceAuthError on failure. Safe to call from within the server."""
+  ts_dir = _ts_dir()
+  _ensure_installed(ts_dir)
+  result = subprocess.run(['npm', 'run', 'booking-2fa'], cwd=ts_dir, env=_npm_env())
+  if result.returncode != 0:
+    from .types import ServiceAuthError
+    raise ServiceAuthError(f'booking-2fa failed (exit {result.returncode}) — SMS gateway may not be configured')
+
+
+def _trigger_booking_chat_auth_cli() -> None:
+  """CLI entry point for `uv run booking-2fa`."""
   ts_dir = _ts_dir()
   _ensure_installed(ts_dir)
   result = subprocess.run(['npm', 'run', 'booking-2fa', '--'] + sys.argv[1:], cwd=ts_dir, env=_npm_env())