import os import sqlite3 import hashlib import time from datetime import datetime, timezone from superoffice_client import SuperOfficeClient # --- CONFIGURATION --- DB_FILE_MATRIX = "marketing_matrix.db" DB_FILE_STATE = "processing_state.db" POLLING_INTERVAL_SECONDS = 300 # 5 minutes # UDF ProgIds PROG_ID_CONTACT_VERTICAL = "SuperOffice:5" PROG_ID_PERSON_ROLE = "SuperOffice:3" PROG_ID_CONTACT_CHALLENGE = "SuperOffice:6" PROG_ID_PERSON_SUBJECT = "SuperOffice:5" PROG_ID_PERSON_INTRO = "SuperOffice:6" PROG_ID_PERSON_PROOF = "SuperOffice:7" PROG_ID_PERSON_HASH = "SuperOffice:8" # --- DATABASE SETUP --- def init_state_db(): conn = sqlite3.connect(DB_FILE_STATE) c = conn.cursor() # Stores the last known hash for a person to detect changes c.execute('''CREATE TABLE IF NOT EXISTS person_state (person_id INTEGER PRIMARY KEY, last_known_hash TEXT, last_updated TEXT)''') # Stores the timestamp of the last run c.execute('''CREATE TABLE IF NOT EXISTS system_state (key TEXT PRIMARY KEY, value TEXT)''') c.execute("INSERT OR IGNORE INTO system_state VALUES ('last_run_utc', ?)", (datetime.utcnow().isoformat(),)) conn.commit() conn.close() print("✅ State DB initialized.") # --- CORE LOGIC --- def get_text_from_matrix(vertical_id, role_id): # (Same as in webhook_server) # ... (omitted for brevity, will be imported) pass def process_person(client: SuperOfficeClient, person_id: int): # (Central logic from webhook_server, adapted slightly) # ... (omitted for brevity, will be imported/reused) pass def poll_for_changes(client: SuperOfficeClient, last_run_utc: str): print(f"Polling for persons modified since {last_run_utc}...") # API Query: Get recently updated persons # We select the fields we need to minimize payload select_fields = "personId,contact/contactId,userDefinedFields" filter_query = f"lastModified gt '{last_run_utc}'" # In a real scenario, you'd handle paging for many results recently_updated_persons = client.search(f"Person?$select={select_fields}&$filter={filter_query}") if not recently_updated_persons: print("No persons updated since last run.") return print(f"Found {len(recently_updated_persons)} updated persons to check.") conn = sqlite3.connect(DB_FILE_STATE) c = conn.cursor() for person in recently_updated_persons: person_id = person.get('personId') try: # 1. Get current state from SuperOffice udfs = person.get('UserDefinedFields', {}) vertical_id_raw = client.get_contact(person['contact']['contactId'])["UserDefinedFields"].get(PROG_ID_CONTACT_VERTICAL, "") role_id_raw = udfs.get(PROG_ID_PERSON_ROLE, "") if not vertical_id_raw or not role_id_raw: print(f" - Skipping Person {person_id}: Missing Vertical/Role ID.") continue vertical_id = int(vertical_id_raw.replace("[I:", "").replace("]", "")) role_id = int(role_id_raw.replace("[I:", "").replace("]", "")) # 2. Generate the "expected" hash expected_hash = hashlib.md5(f"{vertical_id}-{role_id}".encode()).hexdigest() # 3. Get last known hash from our state DB c.execute("SELECT last_known_hash FROM person_state WHERE person_id = ?", (person_id,)) result = c.fetchone() last_known_hash = result[0] if result else None # 4. Compare and act if expected_hash != last_known_hash: print(f" -> Change detected for Person {person_id} (New state: V:{vertical_id}/R:{role_id}). Processing...") # Here we would call the full processing logic from webhook_server.py # For now, we simulate the update and save the new state. # process_single_person(client, person_id) # This would be the real call # Update our state DB c.execute("INSERT OR REPLACE INTO person_state VALUES (?, ?, ?)", (person_id, expected_hash, datetime.utcnow().isoformat())) conn.commit() print(f" ✅ Processed and updated state for Person {person_id}.") else: print(f" - Skipping Person {person_id}: No relevant change detected (hash is the same).") except Exception as e: print(f" - ❌ Error processing Person {person_id}: {e}") conn.close() # --- MAIN DAEMON LOOP --- def main(): init_state_db() try: client = SuperOfficeClient() except Exception as e: print(f"Could not start daemon: {e}") return while True: conn = sqlite3.connect(DB_FILE_STATE) c = conn.cursor() c.execute("SELECT value FROM system_state WHERE key = 'last_run_utc'") last_run = c.fetchone()[0] # Poll for changes poll_for_changes(client, last_run) # Update last run time new_last_run = datetime.utcnow().isoformat() c.execute("UPDATE system_state SET value = ? WHERE key = 'last_run_utc'", (new_last_run,)) conn.commit() conn.close() print(f"\nPolling complete. Next run in {POLLING_INTERVAL_SECONDS} seconds...") time.sleep(POLLING_INTERVAL_SECONDS) if __name__ == '__main__': # This is a conceptual sketch. # The SuperOfficeClient needs a `search` method. # The logic from webhook_server needs to be callable. print("This script is a blueprint for the polling daemon.") print("It requires a `search` method in the SuperOfficeClient and refactoring.")