#!/usr/bin/env python3 """Patch wifi_key, wifi_ssid, and/or door_code on a reservation that contains a given date. Usage: uv run python3 scripts/patch_reservation_codes.py 2026-07-05 --wifi stone-angel-market --door 0472 uv run python3 scripts/patch_reservation_codes.py 2026-07-05 --wifi new-word-combo --ssid MyNetwork uv run python3 scripts/patch_reservation_codes.py 2026-07-05 --door 1234 """ import argparse import asyncio import json import sys from datetime import date from room_cal.store import Calendar, DEFAULT_CALENDAR_NAME from room_cal._utils import safe_json_loads, filter_events, service_name _PREFIX = 'Guest: ' def _build_description(xd: dict, wk: str, ws: str | None, dc: str) -> str: svc = xd.get('service', '') wifi_line = f"WiFi: {ws} / {wk}" if ws else f"WiFi: {wk}" return ( f"Name: {xd.get('guest_name', '')}\n" f"Service: {service_name(svc)}\n" f"Reservation ID: {xd.get('reservation_id', '')}\n" f"Door: {dc}\n" f"{wifi_line}" ) async def patch(target: date, new_wifi: str | None, new_ssid: str | None, new_door: str | None, calendar_name: str) -> None: cal = Calendar(calendar_name) events = await cal.fetch_date_range(target, target) reservations = filter_events(events, _PREFIX, all_day=False) if not reservations: print(f"No reservation found overlapping {target}", file=sys.stderr) sys.exit(1) if len(reservations) > 1: reservations.sort(key=lambda e: e['start']) event = reservations[-1] print(f"Multiple reservations overlap {target}, using the later one ({event['start'].date()} – {event['end'].date()})") else: event = reservations[0] xd = safe_json_loads(event['x_data']) or {} old_wifi = xd.get('wifi_key', '(none)') old_ssid = xd.get('wifi_ssid') old_door = xd.get('door_code', '(none)') wk = new_wifi if new_wifi is not None else old_wifi ws = new_ssid if new_ssid is not None else old_ssid dc = new_door if new_door is not None else old_door print(f"Reservation: {event['title']} ({event['start'].date()} – {event['end'].date()})") if new_wifi is not None: print(f" wifi_key: {old_wifi!r} → {wk!r}") if new_ssid is not None: print(f" wifi_ssid: {old_ssid!r} → {ws!r}") if new_door is not None: print(f" door_code: {old_door!r} → {dc!r}") confirm = input("Apply? [y/N] ").strip().lower() if confirm != 'y': print("Aborted.") return xd['wifi_key'] = wk xd['wifi_ssid'] = ws xd['door_code'] = dc new_x_data = json.dumps(xd) new_description = _build_description(xd, wk, ws, dc) await cal.update_event(event, description=new_description, x_data=new_x_data) print("Updated.") def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('date', type=date.fromisoformat, help='Any date within the reservation (YYYY-MM-DD)') parser.add_argument('--wifi', metavar='WIFI_KEY', help='New wifi_key string') parser.add_argument('--ssid', metavar='WIFI_SSID', help='New wifi_ssid string') parser.add_argument('--door', metavar='DOOR_CODE', help='New door_code string') parser.add_argument('--calendar', default=DEFAULT_CALENDAR_NAME, help=f'Calendar name (default: {DEFAULT_CALENDAR_NAME})') args = parser.parse_args() if args.wifi is None and args.ssid is None and args.door is None: parser.error('Provide at least one of --wifi, --ssid, or --door') asyncio.run(patch(args.date, args.wifi, args.ssid, args.door, args.calendar)) if __name__ == '__main__': main()