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
This commit is contained in:
193
book_at_midnight.py
Normal file
193
book_at_midnight.py
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Precision midnight booking for CitaPrevia Portugal Tardes (cola I).
|
||||
Runs in background, pre-establishes TLS at 23:59:55, fires at 00:00:00.
|
||||
|
||||
User: Humberto Normey Gomez, NIE X2491454W
|
||||
Service: S (SAIC), Cola: I (Portugal Tardes, Padron), Fecha: 19-10-2026
|
||||
"""
|
||||
import http.client
|
||||
import ssl
|
||||
import time
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# ── User Data ──────────────────────────────────────────────
|
||||
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"
|
||||
|
||||
# Slots in priority order: 16:00 first, then earliest first
|
||||
HORAS = ["16:00", "14:30", "14:45", "15:00", "15:15", "15:30", "15:45", "16:15", "16:30"]
|
||||
|
||||
HOST = "webservices.alicante.es"
|
||||
PORT = 443
|
||||
PATH = "/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScCitaVo1"
|
||||
|
||||
CT = "application/vnd.oracle.adf.action+json"
|
||||
|
||||
# ── Target: Oct 4, 2026 00:00:00 CEST ─────────────────────
|
||||
target_ts = datetime(2026, 10, 4, 0, 0, 0).timestamp()
|
||||
preconnect_ts = target_ts - 5 # pre-establish TLS 5s before midnight
|
||||
|
||||
def log(msg):
|
||||
ts = datetime.now().strftime('%H:%M:%S.%f')[:-3]
|
||||
print(f"[{ts}] {msg}", flush=True)
|
||||
|
||||
def build_body(hora, motivo_ts):
|
||||
data = {
|
||||
"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": f"{motivo_ts} {COMENTARIO}"},
|
||||
{"procedencia":"NULO"}
|
||||
]
|
||||
}
|
||||
return json.dumps(data, separators=(',', ':'))
|
||||
|
||||
def build_check_body(hora):
|
||||
"""getHorasCitas to check availability"""
|
||||
data = {
|
||||
"name": "getHorasCitas",
|
||||
"parameters": [
|
||||
{"servicio": SERVICIO},
|
||||
{"cola": COLA},
|
||||
{"fecha": FECHA}
|
||||
]
|
||||
}
|
||||
return json.dumps(data, separators=(',', ':'))
|
||||
|
||||
# ── Main ───────────────────────────────────────────────────
|
||||
log("Starting midnight booking bot")
|
||||
log(f"Target midnight: {datetime.fromtimestamp(target_ts)}")
|
||||
log(f"Pre-connect time: {datetime.fromtimestamp(preconnect_ts)}")
|
||||
|
||||
# Phase 1: Sleep until 5 seconds before midnight
|
||||
now = time.time()
|
||||
if now < preconnect_ts:
|
||||
sleep_dur = preconnect_ts - now
|
||||
log(f"Sleeping {sleep_dur:.0f}s until pre-connect phase...")
|
||||
time.sleep(sleep_dur)
|
||||
|
||||
# Phase 2: Pre-establish TLS connection
|
||||
log("Pre-establishing TLS connection...")
|
||||
context = ssl.create_default_context()
|
||||
conn = http.client.HTTPSConnection(HOST, PORT, context=context, timeout=12)
|
||||
try:
|
||||
conn.connect()
|
||||
log("TLS connection established ✓")
|
||||
except Exception as e:
|
||||
log(f"Connection failed: {e}")
|
||||
log("Will retry at midnight...")
|
||||
conn = None
|
||||
|
||||
# Phase 3: Busy-wait until midnight
|
||||
log("Entering busy-wait until midnight...")
|
||||
while time.time() < target_ts:
|
||||
pass
|
||||
|
||||
# Phase 4: At midnight — fire requests
|
||||
log("=== MIDNIGHT! Firing booking requests ===")
|
||||
success = False
|
||||
|
||||
for hora in HORAS:
|
||||
motivo_ts = datetime.now().strftime('%H:%M:%S')
|
||||
body = build_body(hora, motivo_ts)
|
||||
|
||||
if conn is None:
|
||||
# Fallback: re-establish connection
|
||||
log(f"Reconnecting for {hora}...")
|
||||
context = ssl.create_default_context()
|
||||
conn = http.client.HTTPSConnection(HOST, PORT, context=context, timeout=12)
|
||||
conn.connect()
|
||||
|
||||
headers = {
|
||||
"Content-Type": CT,
|
||||
"Origin": "https://citaprevia.alicante.es",
|
||||
"Referer": "https://citaprevia.alicante.es/CitaPrevia/",
|
||||
}
|
||||
|
||||
log(f"→ Attempting: {FECHA} at {hora}")
|
||||
try:
|
||||
conn.request("POST", PATH, body=body, headers=headers)
|
||||
response = conn.getresponse()
|
||||
raw = response.read().decode('utf-8')
|
||||
|
||||
# Parse response
|
||||
try:
|
||||
resp_json = json.loads(raw)
|
||||
result = resp_json.get('result', '')
|
||||
except:
|
||||
result = raw[:200] if raw else "(empty response)"
|
||||
|
||||
log(f" Response ({response.status}): {result}")
|
||||
|
||||
if result == "Cita insertada":
|
||||
log(f"")
|
||||
log(f"*** ✅ CITA BOOKED! ***")
|
||||
log(f" Servicio: {SERVICIO} (SAC/SAIC)")
|
||||
log(f" Cola: {COLA} (C/PORTUGAL 17 TARDE - PADRÓN)")
|
||||
log(f" Fecha: {FECHA}")
|
||||
log(f" Hora: {hora}")
|
||||
log(f" Nombre: {NOMBRE} {APE1} {APE2}")
|
||||
log(f" NIE: {DOC}")
|
||||
log(f" Motivo: {motivo_ts} {COMENTARIO}")
|
||||
log(f"")
|
||||
log(f" Verify: getHorasCitas for {FECHA} — slot {hora} should be gone")
|
||||
log(f" View cita: https://citaprevia.alicante.es/CitaPrevia/#/verCita/{DOC}/{SERVICIO}{COLA}{DOC}/OLD")
|
||||
success = True
|
||||
break
|
||||
|
||||
elif result == "Fecha posterior al limite permitido":
|
||||
log(f" → Timing issue (server clock?). Retrying immediately...")
|
||||
time.sleep(0.05)
|
||||
|
||||
elif result == "Hora no permitida":
|
||||
log(f" → Slot taken or invalid. Trying next...")
|
||||
|
||||
else:
|
||||
log(f" → Unexpected response. Stopping.")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
log(f" → Error: {e}")
|
||||
# Try re-establishing connection
|
||||
try:
|
||||
conn.close()
|
||||
context = ssl.create_default_context()
|
||||
conn = http.client.HTTPSConnection(HOST, PORT, context=context, timeout=12)
|
||||
conn.connect()
|
||||
log(" Connection re-established.")
|
||||
except:
|
||||
log(" Failed to re-establish connection.")
|
||||
break
|
||||
|
||||
if not success:
|
||||
log("")
|
||||
log("❌ All slots failed. The 9 slots at Oct 19 may have been grabbed by bots.")
|
||||
log(" Check getHorasCitas manually for remaining availability.")
|
||||
|
||||
log("Script finished.")
|
||||
121
book_cita.sh
Executable file
121
book_cita.sh
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Book Portugal Tardes (cola I) cita for Humberto Normey Gomez
|
||||
# Runs at Oct 4 00:00:00 — when Oct 19 enters the 15-day booking window
|
||||
#
|
||||
# Usage: bash /tmp/book_cita.sh
|
||||
#
|
||||
# Strategy: try 16:00 first (user's requested time), then sweep
|
||||
# backwards through all 9 available slots to beat bot hoarding.
|
||||
#
|
||||
|
||||
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
BASE="https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1"
|
||||
CT="Content-Type: application/vnd.oracle.adf.action+json"
|
||||
|
||||
# User data
|
||||
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"
|
||||
|
||||
# Slots to try, in priority order (16:00 first, then earlier)
|
||||
HORAS=("16:00" "15:45" "15:30" "15:15" "15:00" "14:45" "14:30" "16:15" "16:30")
|
||||
|
||||
echo "============================================"
|
||||
echo " CitaPrevia Alicante — Auto Booking Script"
|
||||
echo " Cola I (Portugal Tardes, Padron)"
|
||||
echo " $FECHA"
|
||||
echo " $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
for HORA in "${HORAS[@]}"; do
|
||||
TS=$(python3 -c "from datetime import datetime; print(datetime.now().strftime('%H:%M:%S'))")
|
||||
MOTIVO="${TS} ${COMENTARIO}"
|
||||
|
||||
echo "--- Attempting: $FECHA at $HORA ---"
|
||||
|
||||
RESPONSE=$(timeout 15 curl -sS --max-time 12 --connect-timeout 5 \
|
||||
-A "$UA" \
|
||||
-H "$CT" \
|
||||
-H "Origin: https://citaprevia.alicante.es" \
|
||||
-H "Referer: https://citaprevia.alicante.es/CitaPrevia/" \
|
||||
-X POST "$BASE/ScCitaVo1" \
|
||||
-d "{\"name\":\"insertarCitaPrevia\",\"parameters\":[\
|
||||
{\"servicio\":\"$SERVICIO\"},\
|
||||
{\"cola\":\"$COLA\"},\
|
||||
{\"fecha\":\"$FECHA\"},\
|
||||
{\"hora\":\"$HORA\"},\
|
||||
{\"documento\":\"$DOC\"},\
|
||||
{\"ip\":\"0.0.0.0\"},\
|
||||
{\"nombre\":\"$NOMBRE\"},\
|
||||
{\"apellido1\":\"$APE1\"},\
|
||||
{\"apellido2\":\"$APE2\"},\
|
||||
{\"tipo\":\"$TIPO\"},\
|
||||
{\"telefono\":\"$TEL\"},\
|
||||
{\"email\":\"$EMAIL\"},\
|
||||
{\"impuestos\":\"$IMPUESTOS\"},\
|
||||
{\"motivo\":\"$MOTIVO\"},\
|
||||
{\"procedencia\":\"NULO\"}]}" 2>/dev/null)
|
||||
|
||||
RESULT=$(echo "$RESPONSE" | python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
d = json.load(sys.stdin)
|
||||
print(d.get('result', '(no result field)'))
|
||||
except:
|
||||
print('PARSE_ERROR')
|
||||
" 2>/dev/null)
|
||||
|
||||
echo " Response: $RESULT"
|
||||
|
||||
if [ "$RESULT" = "Cita insertada" ]; then
|
||||
echo ""
|
||||
echo "*** SUCCESS! Cita booked for $FECHA at $HORA ***"
|
||||
echo ""
|
||||
echo "=== Booking Details ==="
|
||||
echo " Servicio: $SERVICIO (SAC - SAIC)"
|
||||
echo " Cola: $COLA (Portugal Tardes - Padron)"
|
||||
echo " Fecha: $FECHA"
|
||||
echo " Hora: $HORA"
|
||||
echo " Nombre: $NOMBRE $APE1 $APE2"
|
||||
echo " NIE: $DOC"
|
||||
echo " Telefono: $TEL"
|
||||
echo " Email: $EMAIL"
|
||||
echo " Motivo: $MOTIVO"
|
||||
echo ""
|
||||
echo "=== To verify later, check with: ==="
|
||||
echo " GET $BASE/ScServicioVo1/$SERVICIO/child/ScColaVo/$COLA "
|
||||
echo " Or call getHorasCitas for $FECHA to confirm the slot is no longer listed."
|
||||
echo ""
|
||||
echo "=== To view/print your cita (from the frontend, once it's back up): ==="
|
||||
echo " https://citaprevia.alicante.es/CitaPrevia/#/verCita/$DOC/$SERVICIO$COLA$DOC/OLD"
|
||||
echo ""
|
||||
exit 0
|
||||
elif [ "$RESULT" = "Fecha posterior al limite permitido" ]; then
|
||||
echo " → Still beyond the 15-day window. Wait for midnight."
|
||||
exit 1
|
||||
elif [ "$RESULT" = "Hora no permitida" ]; then
|
||||
echo " → This slot is already taken."
|
||||
elif echo "$RESULT" | grep -q "POSTULANTE"; then
|
||||
echo " → Cita y Bono already exists for this person. Check existing cita."
|
||||
exit 2
|
||||
else
|
||||
echo " → Other response. Stopping."
|
||||
echo " Full response: $RESULT"
|
||||
exit 3
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=== All 9 slots were taken. Try again tomorrow for next working day. ==="
|
||||
exit 4
|
||||
36
booking_log.txt
Normal file
36
booking_log.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
Booking log — 2026-10-04 00:00:03
|
||||
NIE: X2491454W Cola: I Fecha: 19-10-2026
|
||||
Tracking: None (clean motivo)
|
||||
|
||||
=== ATTEMPTS ===
|
||||
{
|
||||
"hora": "15:15",
|
||||
"status": 200,
|
||||
"response": "Cita insertada",
|
||||
"elapsed_ms": 3347.3,
|
||||
"timestamp": "00:00:03.307"
|
||||
}
|
||||
|
||||
=== TIMING ===
|
||||
23:59:57.389 [ 389.3ms] [ REQUEST] TLS_CONNECT: webservices.alicante.es:443
|
||||
23:59:59.718 [ 218.1ms] [ REQUEST] CHECK_SLOTS: 218ms — [{"hora":"14:30","dia":"L","horanumero":"1430"},{"hora":"14:45","dia":"L","horanumero":"1445"},{"hor
|
||||
00:00:03.307 [ 3347.3ms] [ REQUEST] BOOKING_ATTEMPT: 15:15 → 3347ms — Cita insertada
|
||||
00:00:03.474 [ 166.8ms] [ REQUEST] VERIFY: 167ms
|
||||
|
||||
=== PER-SLOT TIMING ===
|
||||
{
|
||||
"hora": "15:15",
|
||||
"total_ms": 3347.3,
|
||||
"attempts": 1,
|
||||
"avg_rtt_ms": 3347.3,
|
||||
"status": "booked",
|
||||
"rtts": [
|
||||
3347.2704887390137
|
||||
]
|
||||
}
|
||||
|
||||
=== SERVER LOAD ===
|
||||
RTT observations: 4
|
||||
Current load: HIGH (avg=1770ms σ=1578ms)
|
||||
Session avg RTT: 1769.8ms
|
||||
Load trend: STABLE (±1%)
|
||||
25
fetch_cita.sh
Executable file
25
fetch_cita.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
URL="https://citaprevia.alicante.es/citaprevia/pedir-cita"
|
||||
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
MAX=25
|
||||
for i in $(seq 1 $MAX); do
|
||||
echo "=== Attempt $i of $MAX ==="
|
||||
curl -sSL -A "$UA" "$URL" -o /tmp/cita_previa.html \
|
||||
-w "HTTP:%{http_code} SIZE:%{size_download} TIME:%{time_total}\n" \
|
||||
--max-time 45 --connect-timeout 15
|
||||
CODE=$?
|
||||
if [ $CODE -eq 0 ]; then
|
||||
SIZE=$(wc -c < /tmp/cita_previa.html)
|
||||
if [ "$SIZE" -gt 5000 ]; then
|
||||
echo "SUCCESS: got $SIZE bytes"
|
||||
break
|
||||
else
|
||||
echo "Got $SIZE bytes (too small), retrying..."
|
||||
fi
|
||||
elif [ $CODE -eq 28 ]; then
|
||||
echo "timeout, retrying..."
|
||||
else
|
||||
echo "curl error $CODE, retrying..."
|
||||
fi
|
||||
sleep $((i > 5 ? 5 : 3))
|
||||
done
|
||||
142
fetch_final.py
Normal file
142
fetch_final.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import feedparser
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
|
||||
KEYWORDS = ['zil', 'zilliqa', 'harmony', 'stx', 'stacks', 'ltc', 'litecoin',
|
||||
'dash', 'enj', 'enjin', 'ong', 'stx', 'stacks', 'algo', 'algorand',
|
||||
'xtz', 'tezos', 'fet', 'fetch', 'chainlink', 'btc', 'bitcoin',
|
||||
'eth', 'ethereum', 'sol', 'solana', 'bnb', 'xrp', 'ada', 'doge',
|
||||
'avax', 'dot', 'matic', 'pol', 'link', 'uni', 'atom', 'near', 'algo', 'icp',
|
||||
'zil', 'zilliqa', 'one', 'harmony', 'enj', 'enjin', 'enjin coin']
|
||||
|
||||
async def fetch_source(session, name, url, cred):
|
||||
try:
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
|
||||
async with session.get(url) as resp:
|
||||
if resp.status != 200:
|
||||
print(f" {name}: HTTP {resp.status}")
|
||||
return []
|
||||
content = await resp.text()
|
||||
except Exception as e:
|
||||
print(f" {name}: ERROR - {e}")
|
||||
return []
|
||||
|
||||
feed = feedparser.parse(content)
|
||||
for entry in feed.entries[:30]:
|
||||
title = entry.get('title', '')
|
||||
summary = entry.get('summary', '')
|
||||
full = f"{title} {summary}".lower()
|
||||
|
||||
found = [kw for kw in ['zil', 'zilliqa', 'harmony', 'stx', 'stacks', 'ltc', 'litecoin',
|
||||
'dash', 'enj', 'enjin', 'ong', 'stx', 'stacks', 'algo', 'algorand',
|
||||
'xtz', 'tezos', 'fet', 'fetch', 'chainlink', 'btc', 'bitcoin',
|
||||
'eth', 'ethereum', 'sol', 'solana', 'bnb', 'xrp', 'ada', 'doge',
|
||||
'avax', 'dot', 'matic', 'pol', 'link', 'uni', 'atom', 'near', 'algo', 'icp',
|
||||
'zil', 'zilliqa', 'one', 'harmony', 'enj', 'enjin', 'enjin coin'] if kw in f"{title} {summary}".lower()]
|
||||
|
||||
if found:
|
||||
pub_ts = None
|
||||
for df in ["published_parsed", "updated_parsed", "created_parsed"]:
|
||||
if entry.get(df):
|
||||
try:
|
||||
dt = datetime(*entry[df][:6], tzinfo=timezone.utc)
|
||||
pub_ts = dt.timestamp()
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not pub_ts:
|
||||
pub_ts = datetime.now(timezone.utc).timestamp()
|
||||
|
||||
return {
|
||||
'source_id': name,
|
||||
'source_credibility': cred,
|
||||
'title': title,
|
||||
'content': f"{title}. {summary}",
|
||||
'url': entry.get('link', ''),
|
||||
'publish_ts': pub_ts,
|
||||
'matched_assets': list(set(found)),
|
||||
}
|
||||
return []
|
||||
|
||||
async def main():
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
|
||||
feeds = [
|
||||
("coindesk", "https://www.coindesk.com/arc/outboundfeeds/rss/", 0.85),
|
||||
("cointelegraph", "https://cointelegraph.com/rss", 0.75),
|
||||
("decrypt", "https://decrypt.co/feed", 0.75),
|
||||
("glassnode", "https://insights.glassnode.com/rss/", 0.85),
|
||||
("wsj_crypto", "https://feeds.a.dj.com/rss/RSSMarketsMain.xml", 0.85),
|
||||
]
|
||||
|
||||
all_articles = []
|
||||
for name, url, cred in [
|
||||
("coindesk", "https://www.coindesk.com/arc/outboundfeeds/rss/", 0.85),
|
||||
("cointelegraph", "https://cointelegraph.com/rss", 0.75),
|
||||
("decrypt", "https://decrypt.co/feed", 0.75),
|
||||
("glassnode", "https://insights.glassnode.com/rss/", 0.85),
|
||||
("wsj_crypto", "https://feeds.a.dj.com/rss/RSSMarketsMain.xml", 0.85),
|
||||
]:
|
||||
try:
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
|
||||
async with session.get(url) as resp:
|
||||
if resp.status != 200:
|
||||
print(f" {name}: HTTP {resp.status}")
|
||||
continue
|
||||
content = await resp.text()
|
||||
except Exception as e:
|
||||
print(f" {name}: ERROR - {e}")
|
||||
continue
|
||||
|
||||
feed = feedparser.parse(content)
|
||||
for entry in feed.entries[:30]:
|
||||
title = entry.get('title', '')
|
||||
summary = entry.get('summary', '')
|
||||
full = f"{title} {summary}".lower()
|
||||
|
||||
found = [kw for kw in ['zil', 'zilliqa', 'harmony', 'stx', 'stacks', 'ltc', 'litecoin',
|
||||
'dash', 'enj', 'enjin', 'ong', 'stx', 'stacks', 'algo', 'algorand',
|
||||
'xtz', 'tezos', 'fet', 'fetch', 'chainlink', 'btc', 'bitcoin',
|
||||
'eth', 'ethereum', 'sol', 'solana', 'bnb', 'xrp', 'ada', 'doge',
|
||||
'avax', 'dot', 'matic', 'pol', 'link', 'uni', 'atom', 'near', 'algo', 'icp',
|
||||
'zil', 'zilliqa', 'one', 'harmony', 'enj', 'enjin', 'enjin coin'] if kw in f"{title} {summary}".lower()]
|
||||
|
||||
if found:
|
||||
pub_ts = None
|
||||
for df in ["published_parsed", "updated_parsed", "created_parsed"]:
|
||||
if entry.get(df):
|
||||
try:
|
||||
dt = datetime(*entry[df][:6], tzinfo=timezone.utc)
|
||||
pub_ts = dt.timestamp()
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not pub_ts:
|
||||
pub_ts = datetime.now(timezone.utc).timestamp()
|
||||
|
||||
return {
|
||||
'source_id': name,
|
||||
'source_credibility': cred,
|
||||
'title': title,
|
||||
'content': f"{title}. {summary}",
|
||||
'url': entry.get('link', ''),
|
||||
'publish_ts': pub_ts,
|
||||
'matched_assets': list(set(found)),
|
||||
}
|
||||
except Exception as e:
|
||||
print(f" {name}: ERROR - {e}")
|
||||
|
||||
return []
|
||||
|
||||
articles = asyncio.run(main())
|
||||
print(f"\nTotal articles: {len(articles)}")
|
||||
|
||||
with open('trade_news_20260921_22.json', 'w') as f:
|
||||
json.dump(articles, f, default=str)
|
||||
|
||||
print(f"Saved {len(articles)} articles")
|
||||
for a in articles[:20]:
|
||||
print(f" {a['source_id']}: {a['title'][:80]}... | Assets: {a['matched_assets']}")
|
||||
|
||||
asyncio.run(main())
|
||||
215
fire_at_midnight.py
Normal file
215
fire_at_midnight.py
Normal file
@@ -0,0 +1,215 @@
|
||||
#!/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)
|
||||
69
tipos_doc.json
Normal file
69
tipos_doc.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"items" : [ {
|
||||
"IdTipodoc" : "1",
|
||||
"TipoDocumento" : "NIF",
|
||||
"links" : [ {
|
||||
"rel" : "self",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/1",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
}, {
|
||||
"rel" : "canonical",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/1",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
} ]
|
||||
}, {
|
||||
"IdTipodoc" : "2",
|
||||
"TipoDocumento" : "NIE",
|
||||
"links" : [ {
|
||||
"rel" : "self",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/2",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
}, {
|
||||
"rel" : "canonical",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/2",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
} ]
|
||||
}, {
|
||||
"IdTipodoc" : "3",
|
||||
"TipoDocumento" : "PASAPORTE",
|
||||
"links" : [ {
|
||||
"rel" : "self",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/3",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
}, {
|
||||
"rel" : "canonical",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/3",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
} ]
|
||||
}, {
|
||||
"IdTipodoc" : "4",
|
||||
"TipoDocumento" : "CIF",
|
||||
"links" : [ {
|
||||
"rel" : "self",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/4",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
}, {
|
||||
"rel" : "canonical",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1/4",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "item"
|
||||
} ]
|
||||
} ],
|
||||
"count" : 4,
|
||||
"hasMore" : false,
|
||||
"limit" : 99,
|
||||
"offset" : 0,
|
||||
"links" : [ {
|
||||
"rel" : "self",
|
||||
"href" : "https://webservices.alicante.es/WsCitaPreviaPublic-RESTWebService-context-root/rest/1/ScTipoDocumentoVo1",
|
||||
"name" : "ScTipoDocumentoVo1",
|
||||
"kind" : "collection"
|
||||
} ]
|
||||
}
|
||||
Reference in New Issue
Block a user