323 lines
7.7 KiB
Python
323 lines
7.7 KiB
Python
"""
|
|
Synchronisation Meta Lead Gen vers SQL Server local.
|
|
|
|
Récupère les formulaires actifs et leurs leads via l'API Graph Meta,
|
|
puis les stocke dans SQL Server sans créer de doublons.
|
|
|
|
L'injection vers OscarCRM n'est pas gérée à ce stade.
|
|
"""
|
|
|
|
import os
|
|
from datetime import datetime, timezone
|
|
|
|
import pyodbc
|
|
import requests
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
load_dotenv()
|
|
|
|
META_ACCESS_TOKEN = os.getenv("META_ACCESS_TOKEN")
|
|
META_API_VERSION = os.getenv("META_API_VERSION", "v25.0")
|
|
META_PAGE_ID = os.getenv("META_PAGE_ID")
|
|
|
|
DB_DRIVER = os.getenv("DB_DRIVER", "SQL Server")
|
|
DB_SERVER = os.getenv("DB_SERVER", "localhost")
|
|
DB_PORT = os.getenv("DB_PORT", "1433")
|
|
DB_NAME = os.getenv("DB_NAME", "MetaLeadDb")
|
|
DB_USER = os.getenv("DB_USER", "sa")
|
|
DB_PASSWORD = os.getenv("DB_PASSWORD")
|
|
|
|
|
|
FORM_CAMPUSES = {
|
|
"1016111097572602": "Marseille",
|
|
"1032481262797041": "Nantes",
|
|
"941574848893640": "Cergy",
|
|
"1012012785055468": "Cergy",
|
|
}
|
|
|
|
|
|
def get_database_connection():
|
|
"""Ouvre une connexion vers SQL Server."""
|
|
|
|
connection_string = (
|
|
f"DRIVER={{{DB_DRIVER}}};"
|
|
f"SERVER={DB_SERVER},{DB_PORT};"
|
|
f"DATABASE={DB_NAME};"
|
|
f"UID={DB_USER};"
|
|
f"PWD={DB_PASSWORD};"
|
|
"TrustServerCertificate=yes;"
|
|
)
|
|
|
|
return pyodbc.connect(connection_string)
|
|
|
|
|
|
def call_meta_api(path, params=None):
|
|
"""Exécute un appel GET vers l'API Graph Meta."""
|
|
|
|
url = f"https://graph.facebook.com/{META_API_VERSION}/{path}"
|
|
|
|
request_params = params.copy() if params else {}
|
|
request_params["access_token"] = META_ACCESS_TOKEN
|
|
|
|
response = requests.get(url, params=request_params, timeout=30)
|
|
response.raise_for_status()
|
|
|
|
return response.json()
|
|
|
|
|
|
def parse_meta_datetime(value):
|
|
"""Convertit une date Meta en datetime compatible SQL Server."""
|
|
|
|
if not value:
|
|
return None
|
|
|
|
parsed_date = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z")
|
|
utc_date = parsed_date.astimezone(timezone.utc)
|
|
|
|
return utc_date.replace(tzinfo=None)
|
|
|
|
|
|
def normalize_field_data(field_data):
|
|
"""Convertit field_data Meta en dictionnaire simple."""
|
|
|
|
normalized = {}
|
|
|
|
for field in field_data or []:
|
|
name = field.get("name")
|
|
values = field.get("values", [])
|
|
|
|
if name:
|
|
normalized[name] = values[0] if values else None
|
|
|
|
return normalized
|
|
|
|
|
|
def get_email_from_fields(fields):
|
|
"""Récupère l'email en gérant les variantes possibles du champ Meta."""
|
|
|
|
return (
|
|
fields.get("adresse_e-mail")
|
|
or fields.get("adresse-e-mail")
|
|
or fields.get("adresse_email")
|
|
or fields.get("email")
|
|
)
|
|
|
|
|
|
def fetch_active_forms():
|
|
"""Récupère les formulaires Lead Gen actifs autorisés."""
|
|
|
|
result = call_meta_api(
|
|
f"{META_PAGE_ID}/leadgen_forms",
|
|
{
|
|
"fields": "id,name,status,locale",
|
|
"limit": 100,
|
|
},
|
|
)
|
|
|
|
forms = result.get("data", [])
|
|
|
|
return [
|
|
form for form in forms
|
|
if form.get("status") == "ACTIVE"
|
|
and form.get("id") in FORM_CAMPUSES
|
|
]
|
|
|
|
|
|
def fetch_form_leads(form_id):
|
|
"""Récupère tous les leads d'un formulaire Meta avec pagination."""
|
|
|
|
all_leads = []
|
|
|
|
result = call_meta_api(
|
|
f"{form_id}/leads",
|
|
{
|
|
"fields": "id,created_time,field_data",
|
|
"limit": 100,
|
|
},
|
|
)
|
|
|
|
all_leads.extend(result.get("data", []))
|
|
next_url = result.get("paging", {}).get("next")
|
|
|
|
while next_url:
|
|
response = requests.get(next_url, timeout=30)
|
|
response.raise_for_status()
|
|
|
|
result = response.json()
|
|
all_leads.extend(result.get("data", []))
|
|
next_url = result.get("paging", {}).get("next")
|
|
|
|
return all_leads
|
|
|
|
|
|
def upsert_leadgen_form(connection, form):
|
|
"""Insère ou met à jour un formulaire Meta dans leadgen_forms."""
|
|
|
|
update_query = """
|
|
UPDATE dbo.leadgen_forms
|
|
SET
|
|
page_id = ?,
|
|
name = ?,
|
|
locale = ?,
|
|
status = ?,
|
|
updated_at = SYSDATETIME()
|
|
WHERE meta_form_id = ?
|
|
"""
|
|
|
|
insert_query = """
|
|
INSERT INTO dbo.leadgen_forms (
|
|
meta_form_id,
|
|
page_id,
|
|
name,
|
|
locale,
|
|
status
|
|
)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
"""
|
|
|
|
cursor = connection.cursor()
|
|
|
|
cursor.execute(
|
|
update_query,
|
|
(
|
|
META_PAGE_ID,
|
|
form.get("name"),
|
|
form.get("locale"),
|
|
form.get("status"),
|
|
form.get("id"),
|
|
),
|
|
)
|
|
|
|
if cursor.rowcount == 0:
|
|
cursor.execute(
|
|
insert_query,
|
|
(
|
|
form.get("id"),
|
|
META_PAGE_ID,
|
|
form.get("name"),
|
|
form.get("locale"),
|
|
form.get("status"),
|
|
),
|
|
)
|
|
|
|
connection.commit()
|
|
cursor.close()
|
|
|
|
|
|
def upsert_lead_data(connection, form, lead):
|
|
"""Insère ou met à jour un lead Meta dans lead_data."""
|
|
|
|
fields = normalize_field_data(lead.get("field_data", []))
|
|
campus = FORM_CAMPUSES.get(form.get("id"))
|
|
|
|
update_query = """
|
|
UPDATE dbo.lead_data
|
|
SET
|
|
meta_form_id = ?,
|
|
created_time = ?,
|
|
je_suis = ?,
|
|
nom = ?,
|
|
first_name = ?,
|
|
adresse_email = ?,
|
|
phone_number = ?,
|
|
city = ?,
|
|
campus = ?,
|
|
updated_at = SYSDATETIME()
|
|
WHERE meta_lead_id = ?
|
|
"""
|
|
|
|
insert_query = """
|
|
INSERT INTO dbo.lead_data (
|
|
meta_lead_id,
|
|
meta_form_id,
|
|
created_time,
|
|
je_suis,
|
|
nom,
|
|
first_name,
|
|
adresse_email,
|
|
phone_number,
|
|
city,
|
|
campus,
|
|
status
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, N'RECEIVED')
|
|
"""
|
|
|
|
values_for_update = (
|
|
form.get("id"),
|
|
parse_meta_datetime(lead.get("created_time")),
|
|
fields.get("je_suis"),
|
|
fields.get("nom"),
|
|
fields.get("first_name"),
|
|
get_email_from_fields(fields),
|
|
fields.get("phone_number"),
|
|
fields.get("city"),
|
|
campus,
|
|
lead.get("id"),
|
|
)
|
|
|
|
values_for_insert = (
|
|
lead.get("id"),
|
|
form.get("id"),
|
|
parse_meta_datetime(lead.get("created_time")),
|
|
fields.get("je_suis"),
|
|
fields.get("nom"),
|
|
fields.get("first_name"),
|
|
get_email_from_fields(fields),
|
|
fields.get("phone_number"),
|
|
fields.get("city"),
|
|
campus,
|
|
)
|
|
|
|
cursor = connection.cursor()
|
|
|
|
cursor.execute(update_query, values_for_update)
|
|
|
|
if cursor.rowcount == 0:
|
|
cursor.execute(insert_query, values_for_insert)
|
|
|
|
connection.commit()
|
|
cursor.close()
|
|
|
|
|
|
def synchronize_meta_to_sql_server():
|
|
"""Synchronise les formulaires et les leads Meta vers SQL Server."""
|
|
|
|
connection = get_database_connection()
|
|
|
|
total_forms = 0
|
|
total_leads = 0
|
|
|
|
try:
|
|
forms = fetch_active_forms()
|
|
|
|
for form in forms:
|
|
upsert_leadgen_form(connection, form)
|
|
total_forms += 1
|
|
|
|
leads = fetch_form_leads(form.get("id"))
|
|
|
|
for lead in leads:
|
|
upsert_lead_data(connection, form, lead)
|
|
total_leads += 1
|
|
|
|
print("Synchronisation terminée.")
|
|
print(f"Formulaires traités : {total_forms}")
|
|
print(f"Leads traités : {total_leads}")
|
|
|
|
finally:
|
|
connection.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if not META_ACCESS_TOKEN:
|
|
raise RuntimeError("META_ACCESS_TOKEN est manquant dans le fichier .env.")
|
|
|
|
if not META_PAGE_ID:
|
|
raise RuntimeError("META_PAGE_ID est manquant dans le fichier .env.")
|
|
|
|
if not DB_PASSWORD:
|
|
raise RuntimeError("DB_PASSWORD est manquant dans le fichier .env.")
|
|
|
|
synchronize_meta_to_sql_server() |