Reindexare KB, timestamps cron/habits actualizate din rulările automate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
210 lines
7.9 KiB
Python
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))
|