Files
echo-core/tools/scout_voluntar_check.py
Marius Mutu 553a137206 chore(kb): note noi (youtube, facebook, fișe grup sprijin, email) + tool scout_voluntar_check
Reindexare KB, timestamps cron/habits actualizate din rulările automate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:53:49 +00:00

210 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""
Verifica emailuri noi de inscriere cercetasi bleuMarin Constanta (voluntari +
lista de asteptare copii) si formateaza un mesaj WhatsApp gata de trimis
pentru colegul care se ocupa de voluntari noi.
Nu salveaza nimic in memory/kb/ - doar citeste emailurile nou-sosite (unread),
extrage campurile si le marcheaza ca citite.
Usage:
python3 tools/scout_voluntar_check.py
"""
import imaplib
import email
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from tools.email_process import (
IMAP_SERVER, IMAP_PORT, IMAP_USER, IMAP_PASS, WHITELIST,
decode_mime_header, get_email_body, extract_sender_email,
)
# Ordinea exacta a campurilor asa cum apar in notificarea Gravity Forms
# (text simplu, fara separatori intre eticheta si valoare) pentru fiecare
# formular de pe scout-bleumarin.ro. Daca site-ul schimba formularul,
# aceste liste trebuie actualizate.
LABELS_VOLUNTAR = [
"Email",
"Nume și prenume",
"Vârsta (peste 18 ani)",
"Telefon",
"Câte ore pe lună ești dispus să investești pentru activitățile de cercetași?",
"Skill profesional",
"De unde ai aflat de cercetași?",
"Care este motivația ta de a te înscrie ca voluntar adult?",
"Avem beneficiari copii și tineri din fiecare ciclu de școală (clasele 1-4, clasele 5-8 si liceu), unde consideri că ai putea ajuta?",
"În ce categorie de activități crezi că ai putea aduce un plus de valoare?",
"Care sunt primele 3 valori morale ce te vor însoți în drumul tău alături de cercetași?",
"Care sunt valorile (principile) ce te aștepți să le găsești la ceilalți voluntari?",
]
LABELS_COPIL = [
"Nume și prenume copil",
"Nume și prenume părinte",
"Număr telefon părinte",
"Adresă e-mail părinte",
"În ce clasă va fi la școală în luna octombrie? (începutul anului cercetășesc următor)",
"Data nașterii",
"Care este motivația înscrierii la cercetași?",
"Copilul are frați, colegi de școală sau prieteni la cercetași? Dacă da, completați numele.",
"De unde ați aflat de activitatea noastră?",
]
def extract_fields(body: str, labels: list[str]) -> dict:
"""Extrage valorile dintre etichete consecutive dintr-un text Gravity Forms.
Emailul e text simplu cu line-wrap la ~70 caractere, care poate rupe o
eticheta la mijloc (ex. "Vârsta (peste 18\\nani)") - normalizam orice
secventa de whitespace la un singur spatiu inainte de a cauta etichetele.
"""
body = re.sub(r"\s+", " ", body)
pattern = "|".join(re.escape(l) for l in labels)
matches = list(re.finditer(pattern, body))
fields = {}
for i, m in enumerate(matches):
label = m.group(0)
start = m.end()
end = matches[i + 1].start() if i + 1 < len(matches) else len(body)
fields[label] = body[start:end].strip(" \n\r\t:")
return fields
def extract_id(body: str) -> str:
m = re.search(r"Răspuns #(\d+)", body)
return m.group(1) if m else "?"
def digits_only(s: str) -> str:
return re.sub(r"\D", "", s or "")
def parse_email(body: str, subject: str) -> dict | None:
body = re.sub(r"\s+", " ", body)
subj = subject.lower()
if "voluntari" in subj:
f = extract_fields(body, LABELS_VOLUNTAR)
return {
"tip": "voluntar",
"id": extract_id(body),
"nume": f.get("Nume și prenume", "?"),
"varsta": f.get("Vârsta (peste 18 ani)", "?"),
"telefon": f.get("Telefon", "?"),
"email": f.get("Email", "?"),
"disponibilitate": f.get("Câte ore pe lună ești dispus să investești pentru activitățile de cercetași?", "?"),
"skill": f.get("Skill profesional", "?"),
"sursa": f.get("De unde ai aflat de cercetași?", "?"),
"interes_varsta": f.get("Avem beneficiari copii și tineri din fiecare ciclu de școală (clasele 1-4, clasele 5-8 si liceu), unde consideri că ai putea ajuta?", "?"),
"interes_activitati": f.get("În ce categorie de activități crezi că ai putea aduce un plus de valoare?", "?"),
}
if "așteptare" in subj or "asteptare" in subj:
f = extract_fields(body, LABELS_COPIL)
return {
"tip": "copil",
"id": extract_id(body),
"nume_copil": f.get("Nume și prenume copil", "?"),
"nume_parinte": f.get("Nume și prenume părinte", "?"),
"telefon": f.get("Număr telefon părinte", "?"),
"email": f.get("Adresă e-mail părinte", "?"),
"clasa": f.get("În ce clasă va fi la școală în luna octombrie? (începutul anului cercetășesc următor)", "?"),
"data_nasterii": f.get("Data nașterii", "?"),
"sursa": f.get("De unde ați aflat de activitatea noastră?", "?"),
}
return None
def fetch_unseen_scout_emails() -> list[dict]:
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
mail.login(IMAP_USER, IMAP_PASS)
mail.select("INBOX")
status, messages = mail.search(None, "UNSEEN")
email_ids = messages[0].split() if messages[0] else []
records = []
seen_ids = []
for eid in email_ids:
status, data = mail.fetch(eid, "(BODY.PEEK[])")
if status != "OK":
continue
msg = email.message_from_bytes(data[0][1])
from_addr = decode_mime_header(msg["From"])
sender_email = extract_sender_email(from_addr)
subject = decode_mime_header(msg["Subject"])
body = get_email_body(msg)
is_forward = "fwd:" in subject.lower()
if sender_email not in WHITELIST and not is_forward:
continue
if "scout-bleumarin.ro" not in body:
continue
parsed = parse_email(body, subject)
if parsed:
records.append(parsed)
seen_ids.append(eid)
# Marcheaza doar emailurile procesate ca citite
for eid in seen_ids:
mail.store(eid, "+FLAGS", "\\Seen")
mail.logout()
return records
def format_message(records: list[dict]) -> str:
if not records:
return "Niciun email nou de inscriere cercetasi."
voluntari = [r for r in records if r["tip"] == "voluntar"]
copii = [r for r in records if r["tip"] == "copil"]
blocks = []
for v in voluntari:
copii_asociati = [c for c in copii if digits_only(c["telefon"]) == digits_only(v["telefon"])]
lines = [
f"Voluntar nou inscris - bleuMarin Constanta (ID #{v['id']})",
"",
f"Nume: {v['nume']}, {v['varsta']} ani",
f"Tel: {v['telefon']}",
f"Email: {v['email']}",
"",
f"Disponibilitate: {v['disponibilitate']}",
f"Skill: {v['skill']}",
f"Interes: {v['interes_varsta']} - {v['interes_activitati']}",
f"De unde a aflat: {v['sursa']}",
]
for c in copii_asociati:
lines.append("")
lines.append(
f"Notă: are și un copil pe lista de așteptare (ID #{c['id']}) - "
f"{c['nume_copil']} (n. {c['data_nasterii']}, clasa din octombrie: {c['clasa']}). "
f"De unde a aflat: {c['sursa']}"
)
blocks.append("\n".join(lines))
copii = [c for c in copii if c not in copii_asociati]
for c in copii:
blocks.append(
"\n".join([
f"Copil nou pe lista de asteptare - bleuMarin Constanta (ID #{c['id']})",
"",
f"Copil: {c['nume_copil']} (n. {c['data_nasterii']})",
f"Parinte: {c['nume_parinte']}",
f"Tel: {c['telefon']}",
f"Email: {c['email']}",
f"Clasa din octombrie: {c['clasa']}",
f"De unde a aflat: {c['sursa']}",
])
)
return "\n\n---\n\n".join(blocks)
if __name__ == "__main__":
records = fetch_unseen_scout_emails()
print(format_message(records))