Files
citas-getter/fire_at_midnight.py
cc-ultrav-1 3c960c1ce5 Add booking artifacts, API data, and catalog files
- booking_log.txt: midnight run results (cita booked at 15:15, 3347ms RTT)
- fire_at_midnight.py: original midnight launch script
- book_cita.sh / book_at_midnight.py: booking shell scripts
- fetch_final.py / fetch_cita.sh: API fetch utilities
- services_full.js, controllers_full.js, app_real2.js: frontend source
- services_real.js, url.js: API endpoint references
- tipos_doc.json: document type data from API
- benchmark_results.json: server load profiling results
2026-10-04 02:40:30 +02:00

216 lines
7.5 KiB
Python

#!/usr/bin/env python3
"""
Midnight cita booking for Portugal Tardes (cola I), Oct 19, 2026.
Hardened against:
- Load-induced TLS/response delays (20s timeout, pre-established TLS)
- Duplicate bookings (sequential, availability check before booking, tracking ID)
- Unknown API responses (explicit success detection, graceful failure)
- Bot competition (sweeps all 9 slots if first is taken)
Usage: Execute at 23:59:56 with `python3 /tmp/fire_at_midnight.py`
"""
import http.client, ssl, json, time, sys, uuid
from datetime import datetime
# ── Configuration ──────────────────────────────────────────
HOST = "webservices.alicante.es"
PORT = 443
API_PATH = "/WsCitaPreviaPublic-RESTWebService-context-root/rest/1"
CITA_PATH = API_PATH + "/ScCitaVo1"
CT = "application/vnd.oracle.adf.action+json"
SERVICIO = "S"
COLA = "I"
FECHA = "19-10-2026"
DOC = "X2491454W"
NOMBRE = "Humberto"
APE1 = "Normey"
APE2 = "Gomez"
TIPO = "2" # NIE
TEL = "687915599"
EMAIL = "hj@normey.net"
IMPUESTOS = "N"
COMENTARIO = "Solicitud de Alta en el Padrón"
IP = "0.0.0.0"
# Unique tracking reference to identify/reconcile duplicates
TRACKING_ID = f"HNG-{uuid.uuid4().hex[:8]}"
# All 9 known slots (verified via getHorasCitas)
ALL_HORAS = ["14:30","14:45","15:00","15:15","15:30","15:45","16:00","16:15","16:30"]
# Priority order: 16:00 first (user's request), then 14:30, 14:45, etc.
PRIORITY = ["16:00","14:30","14:45","15:00","15:15","15:30","15:45","16:15","16:30"]
# ── Target: Oct 4, 2026 00:00:00 CEST ─────────────────────
target_ts = datetime(2026, 10, 4, 0, 0, 0).timestamp()
# ── Response constants (from controllers.js line 430) ─────
R_SUCCESS = "Cita insertada"
R_DATE_LIMIT = "Fecha posterior al limite permitido"
R_TIME_INVALID = "Hora no permitida"
def log(msg):
ts = datetime.now().strftime('%H:%M:%S.%f')[:-3]
print(f"[{ts}] {msg}", flush=True)
def build_booking_body(hora):
motivo = f"{datetime.now().strftime('%H:%M:%S')} {COMENTARIO} [{TRACKING_ID}]"
return json.dumps({
"name": "insertarCitaPrevia",
"parameters": [
{"servicio": SERVICIO},
{"cola": COLA},
{"fecha": FECHA},
{"hora": hora},
{"documento": DOC},
{"ip": IP},
{"nombre": NOMBRE},
{"apellido1": APE1},
{"apellido2": APE2},
{"tipo": TIPO},
{"telefono": TEL},
{"email": EMAIL},
{"impuestos": IMPUESTOS},
{"motivo": motivo},
{"procedencia": "NULO"},
]
}, separators=(',', ':'))
def build_check_body():
"""getHorasCitas to check current availability"""
return json.dumps({
"name": "getHorasCitas",
"parameters": [
{"servicio": SERVICIO},
{"cola": COLA},
{"fecha": FECHA},
]
}, separators=(',', ':'))
def make_request(conn, body, timeout_note=""):
"""Send POST and return (result_string, raw_response)"""
headers = {
"Content-Type": CT,
"Origin": "https://citaprevia.alicante.es",
"Referer": "https://citaprevia.alicante.es/CitaPrevia/",
}
try:
conn.request("POST", CITA_PATH, body=body, headers=headers)
resp = conn.getresponse()
raw = resp.read().decode()
try:
result = json.loads(raw).get('result', '')
except:
result = raw[:300] if raw else "(empty)"
return result, raw, resp.status
except Exception as e:
return f"EXCEPTION: {e}", "", 0
def pre_connect():
"""Establish TLS connection (done before midnight to save handshake time)"""
try:
ctx = ssl.create_default_context()
conn = http.client.HTTPSConnection(HOST, PORT, context=ctx, timeout=20)
conn.connect()
return conn, ctx
except Exception as e:
log(f"Pre-connect failed: {e}")
return None, None
# ── Main ───────────────────────────────────────────────────
now = datetime.now()
log(f"Bot starting Tracking ID: {TRACKING_ID}")
log(f"Target midnight: {datetime.fromtimestamp(target_ts)}")
log(f"Time until midnight: {target_ts - now.timestamp():.1f}s")
log(f"Pre-connecting TLS...")
conn, ctx = pre_connect()
if conn:
log("TLS pre-established ✓")
else:
log("Pre-connect failed — will reconnect at midnight")
log("Busy-waiting until midnight...")
# ── Busy-wait until exactly 00:00:00 ──────────────────────
while time.time() < target_ts:
pass
log("*** MIDNIGHT — Firing ***")
# ── Phase 1: Check available slots (getHorasCitas) ────────
log("Phase 1: Checking available slots via getHorasCitas...")
if not conn:
conn, ctx = pre_connect()
check_body = build_check_body()
result, raw, status = make_request(conn, check_body)
log(f" Availability check: {result[:200]}")
# Parse available slots
available_slots = []
try:
resp_json = json.loads(raw)
slots_raw = json.loads(resp_json.get('result', '[]'))
available_slots = [s['hora'] for s in slots_raw]
log(f" Available slots: {len(available_slots)} — {available_slots}")
except Exception as e:
log(f" Could not parse availability: {e}")
log(f" Will attempt all known slots anyway")
available_slots = ALL_HORAS # fallback
# ── Phase 2: Book in priority order ───────────────────────
# Filter priority list to only available slots
to_try = [h for h in PRIORITY if h in available_slots] or PRIORITY
log(f"Phase 2: Attempting booking in priority order: {to_try}")
booked = False
for hora in to_try:
body = build_booking_body(hora)
log(f" → Booking: {FECHA} at {hora}")
result, raw, status = make_request(conn, body)
log(f" ← Response ({status}): {result[:200]}")
if result == R_SUCCESS:
log("")
log("✅ CITA BOOKED SUCCESSFULLY!")
log(f" Fecha: {FECHA}")
log(f" Hora: {hora}")
log(f" Servicio: {SERVICIO} (SAC/SAIC)")
log(f" Cola: {COLA} (C/PORTUGAL 17 TARDE - PADRÓN)")
log(f" Nombre: {NOMBRE} {APE1} {APE2}")
log(f" NIE: {DOC}")
log(f" Tracking: {TRACKING_ID}")
log(f" Motivo: <timestamp> {COMENTARIO} [{TRACKING_ID}]")
log("")
log(f" To verify: I'll re-check getHorasCitas — slot {hora} should be gone")
log(f" To print: https://citaprevia.alicante.es/CitaPrevia/#/verCita/{DOC}/{SERVICIO}{COLA}{DOC}/OLD")
booked = True
break
elif result == R_DATE_LIMIT:
log(f" → Date limit error (unexpected after midnight). Retrying...")
time.sleep(0.05) # brief pause, then retry same slot
elif result == R_TIME_INVALID:
log(f" → Slot {hora} already taken. Trying next...")
else:
log(f" → Unexpected response: {result[:100]} — skipping to next slot")
# Brief pause between attempts (avoid hammering, but keep it fast)
time.sleep(0.02)
if not booked:
log("")
log("❌ ALL ATTEMPTS FAILED")
log(f" Tried {len(to_try)} slots for {FECHA}")
log(f" Tracking ID: {TRACKING_ID}")
log(f" The {len(available_slots)} slots were likely grabbed by bots in <1s")
log(f" Check back tomorrow — new slots open daily for the next working day")
sys.exit(0 if booked else 1)