Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5704d7b4a | |||
| 159080f0c8 | |||
| e4d2073eeb | |||
| 6dc847a151 | |||
| e1a14ec3fc |
@@ -24,6 +24,8 @@ if ($action === 'get') {
|
|||||||
'title' => $p->post_title,
|
'title' => $p->post_title,
|
||||||
'content' => $p->post_content,
|
'content' => $p->post_content,
|
||||||
'status' => $p->post_status,
|
'status' => $p->post_status,
|
||||||
|
'post_type' => $p->post_type,
|
||||||
|
'post_name' => $p->post_name,
|
||||||
'author' => (int)$p->post_author,
|
'author' => (int)$p->post_author,
|
||||||
], JSON_UNESCAPED_UNICODE));
|
], JSON_UNESCAPED_UNICODE));
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|||||||
Executable → Regular
+68
-2
@@ -6,6 +6,12 @@ lo que queda por autor, la cuota de MiniMax y las ventanas que se saltaron.
|
|||||||
|
|
||||||
Entregado por Hermes en modo no-agent (el stdout va directo a Rafa).
|
Entregado por Hermes en modo no-agent (el stdout va directo a Rafa).
|
||||||
Silencio deliberado si no hay nada que contar y todo está en orden.
|
Silencio deliberado si no hay nada que contar y todo está en orden.
|
||||||
|
|
||||||
|
Hermes solo ejecuta scripts que resuelvan DENTRO de ~/.hermes/scripts, y resuelve
|
||||||
|
los symlinks antes de comprobarlo: un enlace a este fichero se bloquea. Por eso
|
||||||
|
~/.hermes/scripts/fea_tts_backlog_report.py es un wrapper que lo llama por
|
||||||
|
subproceso (mismo patrón que feadulta_ga4_daily.py). Este de aquí es el único
|
||||||
|
sitio donde se edita la lógica.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -115,6 +121,61 @@ def logs_24h() -> tuple[int, int, list[str]]:
|
|||||||
return corridas, saltadas, fallos
|
return corridas, saltadas, fallos
|
||||||
|
|
||||||
|
|
||||||
|
def _campo_cron(campo: str, valores: range) -> set[int]:
|
||||||
|
"""Expande un campo de crontab ('*', '*/5', '1,5,6,0', '0-4') a un set."""
|
||||||
|
if campo == "*":
|
||||||
|
return set(valores)
|
||||||
|
out: set[int] = set()
|
||||||
|
for trozo in campo.split(","):
|
||||||
|
paso = 1
|
||||||
|
if "/" in trozo:
|
||||||
|
trozo, p = trozo.split("/", 1)
|
||||||
|
paso = int(p)
|
||||||
|
if trozo == "*":
|
||||||
|
base = list(valores)
|
||||||
|
elif "-" in trozo:
|
||||||
|
a, b = (int(x) for x in trozo.split("-", 1))
|
||||||
|
base = list(range(a, b + 1))
|
||||||
|
else:
|
||||||
|
base = [int(trozo)]
|
||||||
|
out.update(base[::paso] if paso > 1 else base)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def previstas_24h(ahora: datetime | None = None) -> int | None:
|
||||||
|
"""Cuántas ventanas TENÍA que haber corrido el cron en las últimas 24 h.
|
||||||
|
|
||||||
|
Sin esto, los martes/miércoles/jueves (días de carta, sin cron) el informe
|
||||||
|
daba la alarma de 'ninguna ventana dejó rastro' estando todo correcto. La
|
||||||
|
verdad está en el crontab, no aquí: si Rafa cambia los días, esto le sigue.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
linea = next(
|
||||||
|
l for l in subprocess.run(["crontab", "-l"], text=True, capture_output=True,
|
||||||
|
check=True).stdout.splitlines()
|
||||||
|
if "tts_backlog_cron.sh" in l and not l.lstrip().startswith("#"))
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
return None
|
||||||
|
campos = linea.split(None, 5)
|
||||||
|
if len(campos) < 5:
|
||||||
|
return None
|
||||||
|
minutos = _campo_cron(campos[0], range(60))
|
||||||
|
horas = _campo_cron(campos[1], range(24))
|
||||||
|
dows = {d % 7 for d in _campo_cron(campos[4], range(7))} # cron: 0 y 7 = domingo
|
||||||
|
|
||||||
|
ahora = ahora or datetime.now()
|
||||||
|
n = 0
|
||||||
|
for h in range(25):
|
||||||
|
t = (ahora - timedelta(hours=h)).replace(second=0, microsecond=0)
|
||||||
|
for m in minutos:
|
||||||
|
cand = t.replace(minute=m)
|
||||||
|
if not (ahora - timedelta(hours=24) < cand <= ahora):
|
||||||
|
continue
|
||||||
|
if cand.hour in horas and (cand.weekday() + 1) % 7 in dows:
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
hechos = hechos_24h()
|
hechos = hechos_24h()
|
||||||
total = sum(len(v) for v in hechos.values())
|
total = sum(len(v) for v in hechos.values())
|
||||||
@@ -145,11 +206,16 @@ def main() -> int:
|
|||||||
lineas.append("Cuota MiniMax: no se pudo leer")
|
lineas.append("Cuota MiniMax: no se pudo leer")
|
||||||
else:
|
else:
|
||||||
lineas.append(f"Cuota MiniMax: 5h {p5:.0f}% · semana {pw:.0f}%")
|
lineas.append(f"Cuota MiniMax: 5h {p5:.0f}% · semana {pw:.0f}%")
|
||||||
lineas.append(f"Ventanas 24 h: {corridas} ejecutadas, {saltadas} saltadas por cuota")
|
previstas = previstas_24h()
|
||||||
|
de = "" if previstas is None else f" de {previstas} previstas"
|
||||||
|
lineas.append(f"Ventanas 24 h: {corridas} ejecutadas{de}, {saltadas} saltadas por cuota")
|
||||||
|
if previstas == 0:
|
||||||
|
lineas.append(" (sin ventanas previstas: día sin cron, toca carta)")
|
||||||
|
|
||||||
# Una ventana que ni arranca ni se salta es un fallo mudo: el cron no llegó a
|
# Una ventana que ni arranca ni se salta es un fallo mudo: el cron no llegó a
|
||||||
# correr (bit +x, WSL apagada...). Es exactamente lo que pasó el 2-ago.
|
# correr (bit +x, WSL apagada...). Es exactamente lo que pasó el 2-ago.
|
||||||
if corridas == 0 and saltadas == 0:
|
# Solo es alarma si de verdad tocaba correr; si no, es martes.
|
||||||
|
if corridas == 0 and saltadas == 0 and previstas != 0:
|
||||||
lineas.append("")
|
lineas.append("")
|
||||||
lineas.append("⚠️ Ninguna ventana dejó rastro en 24 h. Si tocaba que corriera, "
|
lineas.append("⚠️ Ninguna ventana dejó rastro en 24 h. Si tocaba que corriera, "
|
||||||
f"comprobar: crontab -l | grep tts_backlog · ls -l {CRON}")
|
f"comprobar: crontab -l | grep tts_backlog · ls -l {CRON}")
|
||||||
|
|||||||
@@ -93,7 +93,15 @@ def get_post_text(pid):
|
|||||||
check=True, capture_output=True)
|
check=True, capture_output=True)
|
||||||
subprocess.run(["docker", "cp", f"{CONTAINER}:/tmp/fea_es.json", "/tmp/fea_es.json"], check=True)
|
subprocess.run(["docker", "cp", f"{CONTAINER}:/tmp/fea_es.json", "/tmp/fea_es.json"], check=True)
|
||||||
d = json.load(open("/tmp/fea_es.json"))
|
d = json.load(open("/tmp/fea_es.json"))
|
||||||
raw = re.sub(r"(?i)</p>|<br\s*/?>|</h[1-6]>", "\n", d["content"])
|
# Hard gate: pages and operational/accounting entries are never TTS input.
|
||||||
|
# This also protects explicit --ids queues, which bypass author-backlog SQL.
|
||||||
|
raw_content = d.get("content", "")
|
||||||
|
blocked_markers = ("fea-don-wrap", "fea-ledger", "Haz tu donación")
|
||||||
|
if d.get("post_type") != "post":
|
||||||
|
raise ValueError(f"post #{pid} no es un artículo (post_type={d.get('post_type')!r}); TTS excluido")
|
||||||
|
if d.get("post_name") == "numeros" or any(marker in raw_content for marker in blocked_markers):
|
||||||
|
raise ValueError(f"post #{pid} es contenido de cuentas/donaciones; TTS excluido")
|
||||||
|
raw = re.sub(r"(?i)</p>|<br\s*/?>|</h[1-6]>", "\n", raw_content)
|
||||||
raw = re.sub(r"<[^>]+>", "", raw)
|
raw = re.sub(r"<[^>]+>", "", raw)
|
||||||
raw = re.sub(r"\[[^\]]+\]", "", raw)
|
raw = re.sub(r"\[[^\]]+\]", "", raw)
|
||||||
raw = html.unescape(raw)
|
raw = html.unescape(raw)
|
||||||
|
|||||||
+78
-27
@@ -2,9 +2,10 @@
|
|||||||
# Cron del backlog de TTS por autor (issue rafa/feadulta#188).
|
# Cron del backlog de TTS por autor (issue rafa/feadulta#188).
|
||||||
# Corre cada 5 h los lunes, viernes, sábados y domingos, aprovechando la cuota
|
# Corre cada 5 h los lunes, viernes, sábados y domingos, aprovechando la cuota
|
||||||
# ociosa de MiniMax para locutar artículos antiguos. Por ventana:
|
# ociosa de MiniMax para locutar artículos antiguos. Por ventana:
|
||||||
# 1) Gate de cuota MiniMax: si la semana va alta, o si la ventana de 5 h ya la
|
# 1) Mide la cuota y CALCULA el tamaño de la tanda para llenar la ventana hasta
|
||||||
# está usando alguien (la carta semanal, el backfill de summaraise...), se
|
# el objetivo. Un tamaño fijo desaprovecha: deja la ventana a medias cuando
|
||||||
# salta. El trabajo de fondo nunca le come la cuota al trabajo con dueño.
|
# está libre, y no cabe cuando está medio usada. Al dimensionar por hueco
|
||||||
|
# libre, además, deja de importar dónde caiga el cron respecto a la ventana.
|
||||||
# 2) tts_produce.py --autor ... --max N: la cola sale de la BD, así que esto es
|
# 2) tts_produce.py --autor ... --max N: la cola sale de la BD, así que esto es
|
||||||
# idempotente por construcción — lo ya locutado no vuelve a salir.
|
# idempotente por construcción — lo ya locutado no vuelve a salir.
|
||||||
# SOLO LOCAL: no toca producción. Publicar en prod es sync_audio_to_prod.py, que
|
# SOLO LOCAL: no toca producción. Publicar en prod es sync_audio_to_prod.py, que
|
||||||
@@ -27,16 +28,29 @@ LOCK="$WORK/cron.lock"
|
|||||||
AUTOR="${FEA_TTS_AUTOR:-382}"
|
AUTOR="${FEA_TTS_AUTOR:-382}"
|
||||||
DESDE="${FEA_TTS_DESDE:-2025}"
|
DESDE="${FEA_TTS_DESDE:-2025}"
|
||||||
HASTA="${FEA_TTS_HASTA:-2026}"
|
HASTA="${FEA_TTS_HASTA:-2026}"
|
||||||
BATCH="${FEA_TTS_BATCH:-10}"
|
|
||||||
|
|
||||||
# Gates de cuota (%). El semanal es el techo real del backlog. El de 5 h es
|
# Coste medido de un audio, en DÉCIMAS de punto porcentual (aritmética entera en
|
||||||
# "¿cabe esta tanda en lo que queda de ventana?": medido, cada audio gasta ~3-4 %
|
# bash). Dos tandas de 10 el 2026-08-02: la ventana de 5 h fue 0→45→88 (~4,4 pts
|
||||||
# de la ventana de 5 h, así que un batch de 10 pide ~40 %. Gate en 55 % deja sitio
|
# por audio) y la semanal 10→14→18 (~0,4 pts). Artículos de Fray Marcos de
|
||||||
# y frena solo cuando la ventana ya la está usando otro (la carta semanal son ~28
|
# 4.000-5.000 caracteres; si se locuta a otro autor con textos mucho más largos,
|
||||||
# audios de golpe) o cuando la está llenando este mismo cron. Si se sube BATCH,
|
# revisar estos números con un par de tandas.
|
||||||
# subir también este número o se saltarán ventanas.
|
COSTE_5H="${FEA_TTS_COSTE_5H:-44}"
|
||||||
MAX_SEMANA="${FEA_TTS_MAX_SEMANA:-85}"
|
COSTE_SEM="${FEA_TTS_COSTE_SEM:-4}"
|
||||||
MAX_5H="${FEA_TTS_MAX_5H:-55}"
|
|
||||||
|
# Objetivos de llenado (%). Dejar cuota sin usar al llegar el reset es tirarla.
|
||||||
|
OBJ_5H="${FEA_TTS_OBJ_5H:-90}"
|
||||||
|
OBJ_SEM="${FEA_TTS_OBJ_SEM:-85}"
|
||||||
|
|
||||||
|
# La semanal NO se gasta a tope en cada ventana: se REPARTE entre las ventanas
|
||||||
|
# que quedan hasta su reset. Llenar cada ventana de 5 h al 90 % son ~8 puntos de
|
||||||
|
# semanal, y hay 20 ventanas activas por semana: 160 puntos para un presupuesto
|
||||||
|
# de 85. Sin reparto, domingo y lunes se lo comen y el fin de semana se queda a
|
||||||
|
# cero. Con reparto sale ~10 audios por ventana, y en la última ventana de la
|
||||||
|
# semana el reparto vale todo lo que sobre, así que tampoco queda cuota sin usar.
|
||||||
|
|
||||||
|
# Tope de seguridad por tanda y override manual (FEA_TTS_BATCH fija el tamaño y
|
||||||
|
# se salta el cálculo).
|
||||||
|
MAX_BATCH="${FEA_TTS_MAX_BATCH:-25}"
|
||||||
|
|
||||||
mkdir -p "$WORK"
|
mkdir -p "$WORK"
|
||||||
cd "$REPO" || exit 1
|
cd "$REPO" || exit 1
|
||||||
@@ -47,39 +61,76 @@ if ! flock -n 9; then
|
|||||||
echo "[$(ts)] otra corrida en curso, salto." >> "$LOG"; exit 0
|
echo "[$(ts)] otra corrida en curso, salto." >> "$LOG"; exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[$(ts)] === cron TTS backlog start (autor=$AUTOR $DESDE-$HASTA batch=$BATCH) ===" >> "$LOG"
|
echo "[$(ts)] === cron TTS backlog start (autor=$AUTOR $DESDE-$HASTA) ===" >> "$LOG"
|
||||||
|
|
||||||
# 1) Gate de cuota MiniMax (ventana de 5 h y semanal)
|
# 1) Medir la cuota, y contar cuántas ventanas del cron quedan hasta que se
|
||||||
read -r PCT5 PCTW <<< "$(python3 "$QUOTA" --json --no-local 2>/dev/null | python3 -c '
|
# reinicie la semanal — es el denominador del reparto.
|
||||||
|
read -r PCT5 PCTW HSEM VENTANAS <<< "$(python3 "$QUOTA" --json --no-local 2>/dev/null | python3 -c '
|
||||||
import json, sys
|
import json, sys
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
# DEBEN COINCIDIR CON EL CRONTAB: 0 */5 * * 1,5,6,0
|
||||||
|
HORAS = {0, 5, 10, 15, 20}
|
||||||
|
DIAS = {0, 4, 5, 6} # lun, vie, sab, dom en datetime.weekday()
|
||||||
|
|
||||||
def pct(v):
|
def pct(v):
|
||||||
# OJO: 0.0 es un valor legítimo (ventana entera libre) y es falsy en Python.
|
# OJO: 0.0 es un valor legítimo (ventana entera libre) y es falsy en Python.
|
||||||
# Un `v or 100` aquí aborta la tanda justo cuando hay toda la cuota disponible.
|
# Un `v or 100` aquí aborta la tanda justo cuando hay toda la cuota disponible.
|
||||||
return int(v) if v is not None else 100
|
return int(v) if v is not None else 100
|
||||||
|
|
||||||
try:
|
try:
|
||||||
d = json.load(sys.stdin)
|
d = json.load(sys.stdin)
|
||||||
m = next(p for p in d["providers"] if p["provider"] == "minimax" and p.get("ok"))
|
m = next(p for p in d["providers"] if p["provider"] == "minimax" and p.get("ok"))
|
||||||
print(pct(m.get("five_h_pct")), pct(m.get("week_pct")))
|
horas, ventanas = 999, 1
|
||||||
|
try:
|
||||||
|
fin = datetime.fromisoformat(m["week_reset"]).astimezone()
|
||||||
|
ahora = datetime.now().astimezone()
|
||||||
|
horas = max(int((fin - ahora).total_seconds() // 3600), 0)
|
||||||
|
# Esta corrida cuenta como una; se suman las que quedan programadas.
|
||||||
|
t = (ahora + timedelta(hours=1)).replace(minute=0, second=0, microsecond=0)
|
||||||
|
while t < fin:
|
||||||
|
if t.hour in HORAS and t.weekday() in DIAS:
|
||||||
|
ventanas += 1
|
||||||
|
t += timedelta(hours=1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
print(pct(m.get("five_h_pct")), pct(m.get("week_pct")), horas, ventanas)
|
||||||
except Exception:
|
except Exception:
|
||||||
print(100, 100) # sin lectura fiable de cuota, no se gasta
|
print(100, 100, 999, 1) # sin lectura fiable de cuota, no se gasta
|
||||||
' 2>/dev/null || echo "100 100")"
|
' 2>/dev/null || echo "100 100 999 1")"
|
||||||
echo "[$(ts)] MiniMax 5h=${PCT5}% semana=${PCTW}%" >> "$LOG"
|
[ "${VENTANAS:-0}" -lt 1 ] && VENTANAS=1
|
||||||
|
|
||||||
if [ "${PCTW:-100}" -ge "$MAX_SEMANA" ]; then
|
# 2) Dimensionar la tanda. Dos límites, manda el más restrictivo:
|
||||||
echo "[$(ts)] ABORT: cuota semanal ${PCTW}% >= ${MAX_SEMANA}%, salto esta ventana." >> "$LOG"
|
# - la ventana de 5 h: se llena hasta OBJ_5H aquí y ahora;
|
||||||
exit 0
|
# - la semanal: solo la parte que le toca a esta ventana de lo que queda.
|
||||||
fi
|
CABE_5H=$(( ((OBJ_5H - PCT5) * 10) / COSTE_5H ))
|
||||||
if [ "${PCT5:-100}" -ge "$MAX_5H" ]; then
|
CABE_SEM=$(( (((OBJ_SEM - PCTW) * 10) / VENTANAS) / COSTE_SEM ))
|
||||||
echo "[$(ts)] ABORT: ventana de 5h al ${PCT5}% >= ${MAX_5H}%, no cabe la tanda; salto." >> "$LOG"
|
[ "$CABE_5H" -lt 0 ] && CABE_5H=0
|
||||||
|
[ "$CABE_SEM" -lt 0 ] && CABE_SEM=0
|
||||||
|
|
||||||
|
BATCH=$CABE_5H
|
||||||
|
[ "$CABE_SEM" -lt "$BATCH" ] && BATCH=$CABE_SEM
|
||||||
|
[ "$BATCH" -gt "$MAX_BATCH" ] && BATCH=$MAX_BATCH
|
||||||
|
# Override manual: fija el tamaño y se salta todo el cálculo.
|
||||||
|
[ -n "${FEA_TTS_BATCH:-}" ] && BATCH="$FEA_TTS_BATCH"
|
||||||
|
|
||||||
|
echo "[$(ts)] MiniMax 5h=${PCT5}% semana=${PCTW}% · reset semanal en ${HSEM}h, ${VENTANAS} ventanas por delante" >> "$LOG"
|
||||||
|
echo "[$(ts)] Caben: ${CABE_5H} por la de 5h, ${CABE_SEM} por el reparto semanal → tanda de ${BATCH}" >> "$LOG"
|
||||||
|
|
||||||
|
if [ "$BATCH" -lt 1 ]; then
|
||||||
|
echo "[$(ts)] ABORT: no cabe ni un audio sin pasarse del objetivo; salto esta ventana." >> "$LOG"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 2) Tanda. tts_produce.py ya para solo ante rc 2056/1039 (cuota/rate limit).
|
# 3) Tanda. tts_produce.py ya para solo ante rc 2056/1039 (cuota/rate limit).
|
||||||
|
# Una tanda larga puede desbordar el reset de 5 h (~2,6 min por audio): no pasa
|
||||||
|
# nada, lo que sobra lo absorbe la ventana siguiente y la próxima corrida la
|
||||||
|
# mide y se redimensiona sola. Cortar por tiempo dejaría cuota sin gastar.
|
||||||
echo "[$(ts)] tts_produce --autor $AUTOR --desde $DESDE --hasta $HASTA --max $BATCH ..." >> "$LOG"
|
echo "[$(ts)] tts_produce --autor $AUTOR --desde $DESDE --hasta $HASTA --max $BATCH ..." >> "$LOG"
|
||||||
"$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \
|
"$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \
|
||||||
--max "$BATCH" >> "$LOG" 2>&1
|
--max "$BATCH" >> "$LOG" 2>&1
|
||||||
|
|
||||||
# 3) Cuánto queda tras la tanda (recuento fresco de la BD, barato y sin cuota).
|
# 4) Cuánto queda tras la tanda (recuento fresco de la BD, barato y sin cuota).
|
||||||
QUEDAN="$("$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \
|
QUEDAN="$("$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \
|
||||||
--dry-run 2>/dev/null | sed -n 's/.*Cola: \([0-9]*\) posts.*/\1/p' | tail -1)"
|
--dry-run 2>/dev/null | sed -n 's/.*Cola: \([0-9]*\) posts.*/\1/p' | tail -1)"
|
||||||
echo "[$(ts)] === cron TTS backlog done. Pendientes autor $AUTOR $DESDE-$HASTA: ${QUEDAN:-?} ===" >> "$LOG"
|
echo "[$(ts)] === cron TTS backlog done. Pendientes autor $AUTOR $DESDE-$HASTA: ${QUEDAN:-?} ===" >> "$LOG"
|
||||||
|
|||||||
@@ -151,23 +151,28 @@ function fea_beta_labels(): array {
|
|||||||
'es' => ['region'=>'Aviso Beta','intro'=>'Estamos en','help'=>'¿Nos ayudas a mejorar FeAdulta?',
|
'es' => ['region'=>'Aviso Beta','intro'=>'Estamos en','help'=>'¿Nos ayudas a mejorar FeAdulta?',
|
||||||
'opinion'=>'Dar mi opinión','collab'=>'Colaborar','dismiss'=>'Cerrar aviso','fbregion'=>'Feedback de la página',
|
'opinion'=>'Dar mi opinión','collab'=>'Colaborar','dismiss'=>'Cerrar aviso','fbregion'=>'Feedback de la página',
|
||||||
'close'=>'Cerrar','q'=>'¿Se ve bien esta página?','up'=>'Sí, se ve bien','down'=>'No, hay algo mal',
|
'close'=>'Cerrar','q'=>'¿Se ve bien esta página?','up'=>'Sí, se ve bien','down'=>'No, hay algo mal',
|
||||||
'ph'=>'¿Algo falla o se ve mal? Cuéntanoslo (opcional)','send'=>'Enviar','thanks'=>'¡Gracias por ayudar! 🙏'],
|
'ph'=>'¿Algo falla o se ve mal? Cuéntanoslo (opcional)','send'=>'Enviar','thanks'=>'¡Gracias por ayudar! 🙏',
|
||||||
|
'error'=>'No se pudo enviar. Comprueba tu conexión e inténtalo de nuevo.'],
|
||||||
'en' => ['region'=>'Beta notice','intro'=>'We are in','help'=>'Will you help us improve FeAdulta?',
|
'en' => ['region'=>'Beta notice','intro'=>'We are in','help'=>'Will you help us improve FeAdulta?',
|
||||||
'opinion'=>'Give feedback','collab'=>'Collaborate','dismiss'=>'Close notice','fbregion'=>'Page feedback',
|
'opinion'=>'Give feedback','collab'=>'Collaborate','dismiss'=>'Close notice','fbregion'=>'Page feedback',
|
||||||
'close'=>'Close','q'=>'Does this page look right?','up'=>'Yes, looks good','down'=>'No, something is wrong',
|
'close'=>'Close','q'=>'Does this page look right?','up'=>'Yes, looks good','down'=>'No, something is wrong',
|
||||||
'ph'=>'Something broken or off? Tell us (optional)','send'=>'Send','thanks'=>'Thanks for helping! 🙏'],
|
'ph'=>'Something broken or off? Tell us (optional)','send'=>'Send','thanks'=>'Thanks for helping! 🙏',
|
||||||
|
'error'=>'Could not send. Check your connection and try again.'],
|
||||||
'fr' => ['region'=>'Avis Bêta','intro'=>'Nous sommes en','help'=>'Voulez-vous nous aider à améliorer FeAdulta ?',
|
'fr' => ['region'=>'Avis Bêta','intro'=>'Nous sommes en','help'=>'Voulez-vous nous aider à améliorer FeAdulta ?',
|
||||||
'opinion'=>'Donner mon avis','collab'=>'Collaborer','dismiss'=>'Fermer l’avis','fbregion'=>'Retour sur la page',
|
'opinion'=>'Donner mon avis','collab'=>'Collaborer','dismiss'=>'Fermer l’avis','fbregion'=>'Retour sur la page',
|
||||||
'close'=>'Fermer','q'=>'Cette page s’affiche-t-elle bien ?','up'=>'Oui, c’est bien','down'=>'Non, il y a un problème',
|
'close'=>'Fermer','q'=>'Cette page s’affiche-t-elle bien ?','up'=>'Oui, c’est bien','down'=>'Non, il y a un problème',
|
||||||
'ph'=>'Un souci ou un affichage incorrect ? Dites-le-nous (facultatif)','send'=>'Envoyer','thanks'=>'Merci de votre aide ! 🙏'],
|
'ph'=>'Un souci ou un affichage incorrect ? Dites-le-nous (facultatif)','send'=>'Envoyer','thanks'=>'Merci de votre aide ! 🙏',
|
||||||
|
'error'=>'Échec de l’envoi. Vérifiez votre connexion et réessayez.'],
|
||||||
'it' => ['region'=>'Avviso Beta','intro'=>'Siamo in','help'=>'Ci aiuti a migliorare FeAdulta?',
|
'it' => ['region'=>'Avviso Beta','intro'=>'Siamo in','help'=>'Ci aiuti a migliorare FeAdulta?',
|
||||||
'opinion'=>'Dai la tua opinione','collab'=>'Collabora','dismiss'=>'Chiudi avviso','fbregion'=>'Feedback della pagina',
|
'opinion'=>'Dai la tua opinione','collab'=>'Collabora','dismiss'=>'Chiudi avviso','fbregion'=>'Feedback della pagina',
|
||||||
'close'=>'Chiudi','q'=>'Questa pagina si vede bene?','up'=>'Sì, si vede bene','down'=>'No, c’è qualcosa che non va',
|
'close'=>'Chiudi','q'=>'Questa pagina si vede bene?','up'=>'Sì, si vede bene','down'=>'No, c’è qualcosa che non va',
|
||||||
'ph'=>'Qualcosa non va o si vede male? Faccelo sapere (facoltativo)','send'=>'Invia','thanks'=>'Grazie per l’aiuto! 🙏'],
|
'ph'=>'Qualcosa non va o si vede male? Faccelo sapere (facoltativo)','send'=>'Invia','thanks'=>'Grazie per l’aiuto! 🙏',
|
||||||
|
'error'=>'Invio non riuscito. Controlla la connessione e riprova.'],
|
||||||
'pt' => ['region'=>'Aviso Beta','intro'=>'Estamos em','help'=>'Ajuda-nos a melhorar a FeAdulta?',
|
'pt' => ['region'=>'Aviso Beta','intro'=>'Estamos em','help'=>'Ajuda-nos a melhorar a FeAdulta?',
|
||||||
'opinion'=>'Dar a minha opinião','collab'=>'Colaborar','dismiss'=>'Fechar aviso','fbregion'=>'Feedback da página',
|
'opinion'=>'Dar a minha opinião','collab'=>'Colaborar','dismiss'=>'Fechar aviso','fbregion'=>'Feedback da página',
|
||||||
'close'=>'Fechar','q'=>'Esta página vê-se bem?','up'=>'Sim, vê-se bem','down'=>'Não, há algo errado',
|
'close'=>'Fechar','q'=>'Esta página vê-se bem?','up'=>'Sim, vê-se bem','down'=>'Não, há algo errado',
|
||||||
'ph'=>'Algo falha ou vê-se mal? Conta-nos (opcional)','send'=>'Enviar','thanks'=>'Obrigado por ajudar! 🙏'],
|
'ph'=>'Algo falha ou vê-se mal? Conta-nos (opcional)','send'=>'Enviar','thanks'=>'Obrigado por ajudar! 🙏',
|
||||||
|
'error'=>'Não foi possível enviar. Verifica a ligação e tenta novamente.'],
|
||||||
];
|
];
|
||||||
$lang = function_exists('pll_current_language') ? (string) pll_current_language() : 'es';
|
$lang = function_exists('pll_current_language') ? (string) pll_current_language() : 'es';
|
||||||
return $all[$lang] ?? $all['es'];
|
return $all[$lang] ?? $all['es'];
|
||||||
@@ -211,6 +216,8 @@ add_action('wp_footer', function () {
|
|||||||
font:inherit; font-size:.85rem; resize:vertical; min-height:58px; box-sizing:border-box; }
|
font:inherit; font-size:.85rem; resize:vertical; min-height:58px; box-sizing:border-box; }
|
||||||
#fea-fb .fea-fb-send { background:#8b1a2e; color:#fff; border:1px solid #8b1a2e; border-radius:8px;
|
#fea-fb .fea-fb-send { background:#8b1a2e; color:#fff; border:1px solid #8b1a2e; border-radius:8px;
|
||||||
padding:6px 12px; font-size:.85rem; width:100%; cursor:pointer; }
|
padding:6px 12px; font-size:.85rem; width:100%; cursor:pointer; }
|
||||||
|
#fea-fb .fea-fb-send:disabled { opacity:.6; cursor:default; }
|
||||||
|
#fea-fb .fea-fb-error { color:#8b1a2e; font-size:.82rem; margin:0 0 8px; }
|
||||||
#fea-fb .fea-fb-hp { position:absolute; left:-9999px; }
|
#fea-fb .fea-fb-hp { position:absolute; left:-9999px; }
|
||||||
#fea-fb .fea-fb-close { position:absolute; top:4px; right:8px; border:0; background:none; font-size:1rem; cursor:pointer; padding:2px 4px; line-height:1; }
|
#fea-fb .fea-fb-close { position:absolute; top:4px; right:8px; border:0; background:none; font-size:1rem; cursor:pointer; padding:2px 4px; line-height:1; }
|
||||||
@media (max-width:600px){ #fea-fb{ right:10px; left:10px; max-width:none; } #fea-beta-bar{ font-size:.8rem; } }
|
@media (max-width:600px){ #fea-fb{ right:10px; left:10px; max-width:none; } #fea-beta-bar{ font-size:.8rem; } }
|
||||||
@@ -234,6 +241,7 @@ add_action('wp_footer', function () {
|
|||||||
<div class="fea-fb-more" hidden>
|
<div class="fea-fb-more" hidden>
|
||||||
<input type="text" class="fea-fb-hp" tabindex="-1" autocomplete="off" aria-hidden="true" placeholder="No rellenar">
|
<input type="text" class="fea-fb-hp" tabindex="-1" autocomplete="off" aria-hidden="true" placeholder="No rellenar">
|
||||||
<textarea placeholder="<?php echo esc_attr($t['ph']); ?>"></textarea>
|
<textarea placeholder="<?php echo esc_attr($t['ph']); ?>"></textarea>
|
||||||
|
<p class="fea-fb-error" hidden role="alert"><?php echo esc_html($t['error']); ?></p>
|
||||||
<button type="button" class="fea-fb-send"><?php echo esc_html($t['send']); ?></button>
|
<button type="button" class="fea-fb-send"><?php echo esc_html($t['send']); ?></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="fea-fb-thanks" hidden><?php echo esc_html($t['thanks']); ?></div>
|
<div class="fea-fb-thanks" hidden><?php echo esc_html($t['thanks']); ?></div>
|
||||||
@@ -252,6 +260,8 @@ add_action('wp_footer', function () {
|
|||||||
var moreEl = box.querySelector('.fea-fb-more');
|
var moreEl = box.querySelector('.fea-fb-more');
|
||||||
var votes = box.querySelectorAll('.fea-fb-vote');
|
var votes = box.querySelectorAll('.fea-fb-vote');
|
||||||
var thanks = box.querySelector('.fea-fb-thanks');
|
var thanks = box.querySelector('.fea-fb-thanks');
|
||||||
|
var errorEl = box.querySelector('.fea-fb-error');
|
||||||
|
var sendBtn = box.querySelector('.fea-fb-send');
|
||||||
|
|
||||||
// Mostrar la barra salvo que el usuario la haya descartado antes.
|
// Mostrar la barra salvo que el usuario la haya descartado antes.
|
||||||
try { if (!localStorage.getItem('fea_beta_bar_off')) bar.classList.remove('hidden'); }
|
try { if (!localStorage.getItem('fea_beta_bar_off')) bar.classList.remove('hidden'); }
|
||||||
@@ -273,17 +283,31 @@ add_action('wp_footer', function () {
|
|||||||
moreEl.hidden = false;
|
moreEl.hidden = false;
|
||||||
});});
|
});});
|
||||||
|
|
||||||
box.querySelector('.fea-fb-send').addEventListener('click', function(){
|
sendBtn.addEventListener('click', function(){
|
||||||
if(!chosen) return;
|
if(!chosen) return;
|
||||||
var hp = box.querySelector('.fea-fb-hp').value;
|
var hp = box.querySelector('.fea-fb-hp').value;
|
||||||
var comment = box.querySelector('textarea').value;
|
var comment = box.querySelector('textarea').value;
|
||||||
|
sendBtn.disabled = true;
|
||||||
|
errorEl.hidden = true;
|
||||||
fetch(REST, { method:'POST', headers:{'Content-Type':'application/json'},
|
fetch(REST, { method:'POST', headers:{'Content-Type':'application/json'},
|
||||||
body: JSON.stringify({ vote:chosen, comment:comment, url:location.href, post_id:pid,
|
body: JSON.stringify({ vote:chosen, comment:comment, url:location.href, post_id:pid,
|
||||||
lang:lang, title:document.title, website:hp }) }).catch(function(){});
|
lang:lang, title:document.title, website:hp }) })
|
||||||
box.querySelector('.fea-fb-btns').hidden = true;
|
.then(function(res){
|
||||||
box.querySelector('.fea-fb-q').hidden = true;
|
if(!res.ok) throw new Error('http_' + res.status);
|
||||||
moreEl.hidden = true; thanks.hidden = false;
|
return res.json();
|
||||||
setTimeout(closeCard, 2200);
|
})
|
||||||
|
.then(function(data){
|
||||||
|
if(!data || data.ok !== true) throw new Error('bad_response');
|
||||||
|
box.querySelector('.fea-fb-btns').hidden = true;
|
||||||
|
box.querySelector('.fea-fb-q').hidden = true;
|
||||||
|
moreEl.hidden = true; thanks.hidden = false;
|
||||||
|
setTimeout(closeCard, 2200);
|
||||||
|
})
|
||||||
|
.catch(function(err){
|
||||||
|
if (window.console && console.warn) console.warn('fea-fb submit failed:', err && err.message);
|
||||||
|
errorEl.hidden = false;
|
||||||
|
sendBtn.disabled = false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user