_scripts.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import logging
  2. import os
  3. import subprocess
  4. import sys
  5. import threading
  6. from pathlib import Path
  7. from room_cal._utils import get_config
  8. _log = logging.getLogger(__name__)
  9. def _data_home() -> Path:
  10. override = os.environ.get('ROOM_CAL_DATA_HOME')
  11. return Path(override) if override else Path.home() / '.local' / 'room_cal'
  12. def _ts_dir() -> Path:
  13. return Path(__file__).parent / 'typescript'
  14. def _npm_env() -> dict:
  15. dh = _data_home()
  16. env = os.environ.copy()
  17. env.setdefault('ROOM_CAL_DATA_HOME', str(dh))
  18. env.setdefault('PLAYWRIGHT_BROWSERS_PATH', str(dh / '.playwright-browsers'))
  19. api_url = get_config().api_url
  20. if api_url:
  21. env.setdefault('ROOM_CAL_API_URL', api_url)
  22. return env
  23. def _ensure_installed(ts_dir: Path) -> None:
  24. if not (ts_dir / 'node_modules').exists():
  25. print('=== npm install (service-api) ===')
  26. subprocess.run(['npm', 'install'], cwd=ts_dir, check=True)
  27. print()
  28. # One SMS code is valid at a time and Booking.com rate-limits sends regardless of how often we ask,
  29. # so there is no benefit to concurrent booking browser operations — serialize them all.
  30. _booking_lock = threading.Lock()
  31. def refresh_booking_cookies() -> None:
  32. ts_dir = _ts_dir()
  33. _ensure_installed(ts_dir)
  34. _log.info('booking: starting credential refresh')
  35. with _booking_lock:
  36. try:
  37. subprocess.run(['npm', 'run', 'refresh-booking'], cwd=ts_dir, env=_npm_env(), check=True)
  38. except subprocess.CalledProcessError as e:
  39. _log.error('booking: credential refresh failed (exit %d)', e.returncode)
  40. from .types import ServiceAuthError
  41. raise ServiceAuthError(f'Booking.com credential refresh failed (exit {e.returncode})') from None
  42. _log.info('booking: credential refresh complete')
  43. def refresh_airbnb_cookies() -> None:
  44. ts_dir = _ts_dir()
  45. _ensure_installed(ts_dir)
  46. _log.info('airbnb: starting credential refresh')
  47. env = _npm_env()
  48. env['HEADLESS'] = '1'
  49. try:
  50. subprocess.run(['npm', 'run', 'refresh-airbnb'], cwd=ts_dir, env=env, check=True)
  51. except subprocess.CalledProcessError as e:
  52. _log.error('airbnb: credential refresh failed (exit %d)', e.returncode)
  53. from .types import ServiceAuthError
  54. raise ServiceAuthError(f'Airbnb session expired — re-run without HEADLESS to re-authenticate (exit {e.returncode})') from None
  55. _log.info('airbnb: credential refresh complete')
  56. def refresh_service(key: str) -> None:
  57. """Trigger the automatic credential refresh for the named service. Raises ServiceAuthError on failure."""
  58. handlers = {
  59. 'booking': refresh_booking_cookies,
  60. 'airbnb': refresh_airbnb_cookies,
  61. }
  62. fn = handlers.get(key)
  63. if fn is None:
  64. from .types import ServiceAuthError
  65. raise ServiceAuthError(f'No automatic refresh handler for service: {key}')
  66. fn()
  67. def refresh_airbnb_cli() -> None:
  68. """CLI entry point for `uv run refresh-airbnb`."""
  69. ts_dir = _ts_dir()
  70. _ensure_installed(ts_dir)
  71. result = subprocess.run(['npm', 'run', 'refresh-airbnb', '--'] + sys.argv[1:], cwd=ts_dir, env=_npm_env())
  72. sys.exit(result.returncode)
  73. def refresh_booking_cli() -> None:
  74. """CLI entry point for `uv run refresh-booking`."""
  75. ts_dir = _ts_dir()
  76. _ensure_installed(ts_dir)
  77. result = subprocess.run(['npm', 'run', 'refresh-booking', '--'] + sys.argv[1:], cwd=ts_dir, env=_npm_env())
  78. sys.exit(result.returncode)
  79. def refresh_cookies() -> None:
  80. ts_dir = _ts_dir()
  81. _ensure_installed(ts_dir)
  82. result = subprocess.run(['npm', 'run', 'refresh-all', '--'] + sys.argv[1:], cwd=ts_dir, env=_npm_env())
  83. sys.exit(result.returncode)