137 lines
5.3 KiB
Python
137 lines
5.3 KiB
Python
"""
|
||
main.py — Entry point for the Factors.AI account-export automation.
|
||
|
||
Usage
|
||
─────
|
||
python main.py # uses settings in config.py / .env
|
||
python main.py --visible # opens a real browser window (debug)
|
||
python main.py --dry-run # skip email; just download & compare
|
||
|
||
Schedule (cron example — runs every day at 8 AM)
|
||
─────────────────────────────────────────────────
|
||
0 8 * * * /path/to/venv/bin/python /path/to/factors_automation/main.py
|
||
"""
|
||
import argparse
|
||
import logging
|
||
import shutil
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# ── Logging (set up before any other import) ──────────────────────────────────
|
||
from config import LOG_PATH # noqa: E402 – needs to be early
|
||
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format="%(asctime)s [%(levelname)-8s] %(name)s — %(message)s",
|
||
datefmt="%Y-%m-%d %H:%M:%S",
|
||
handlers=[
|
||
logging.StreamHandler(sys.stdout),
|
||
logging.FileHandler(str(LOG_PATH), encoding="utf-8"),
|
||
],
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# ── Application imports ───────────────────────────────────────────────────────
|
||
from config import ( # noqa: E402
|
||
FACTORS_EMAIL,
|
||
FACTORS_PASSWORD,
|
||
TARGET_PROJECT,
|
||
TEMP_DOWNLOAD_DIR,
|
||
LAST_CSV_PATH,
|
||
SMTP_HOST,
|
||
SMTP_PORT,
|
||
SENDER_EMAIL,
|
||
SENDER_PASSWORD,
|
||
RECIPIENT_EMAILS,
|
||
FIELDS_TO_SELECT,
|
||
HEADLESS,
|
||
)
|
||
from scraper import FactorsAIScraper
|
||
from comparator import find_new_entries
|
||
from mailer import send_alert
|
||
|
||
|
||
# ── CLI arguments ─────────────────────────────────────────────────────────────
|
||
|
||
def _parse_args() -> argparse.Namespace:
|
||
p = argparse.ArgumentParser(description="Factors.AI account-export automation")
|
||
p.add_argument(
|
||
"--visible",
|
||
action="store_true",
|
||
help="Open a real browser window instead of running headless",
|
||
)
|
||
p.add_argument(
|
||
"--dry-run",
|
||
action="store_true",
|
||
help="Download and compare but do NOT send the email",
|
||
)
|
||
return p.parse_args()
|
||
|
||
|
||
# ── Main flow ─────────────────────────────────────────────────────────────────
|
||
|
||
def main() -> None:
|
||
args = _parse_args()
|
||
headless = HEADLESS and not args.visible
|
||
|
||
logger.info("=" * 65)
|
||
logger.info(" Factors.AI Account Export Automation — START")
|
||
logger.info(f" headless={headless} dry_run={args.dry_run}")
|
||
logger.info("=" * 65)
|
||
|
||
# ── Step 1 ─ Download CSV via Selenium ────────────────────────────────────
|
||
logger.info("STEP 1: Downloading CSV …")
|
||
scraper = FactorsAIScraper(
|
||
download_dir=str(TEMP_DOWNLOAD_DIR),
|
||
headless=headless,
|
||
)
|
||
downloaded_csv = scraper.run(
|
||
email=FACTORS_EMAIL,
|
||
password=FACTORS_PASSWORD,
|
||
target_project=TARGET_PROJECT,
|
||
fields_to_select=FIELDS_TO_SELECT,
|
||
)
|
||
logger.info(f" Downloaded → {downloaded_csv}")
|
||
|
||
# ── Step 2 ─ Compare with last export ────────────────────────────────────
|
||
logger.info("STEP 2: Comparing with previous export …")
|
||
last_csv = str(LAST_CSV_PATH) if LAST_CSV_PATH.exists() else None
|
||
new_entries, is_first_run = find_new_entries(downloaded_csv, last_csv)
|
||
|
||
if new_entries.empty and not is_first_run:
|
||
logger.info(" ✓ No new entries found — nothing to mail.")
|
||
else:
|
||
count = len(new_entries)
|
||
tag = "(first run — all entries)" if is_first_run else f"({count} new)"
|
||
logger.info(f" ✓ New entries detected {tag}")
|
||
|
||
# ── Step 3 ─ Send email ───────────────────────────────────────────────
|
||
if args.dry_run:
|
||
logger.info("STEP 3: [DRY RUN] Email skipped.")
|
||
logger.info(new_entries.to_string())
|
||
else:
|
||
logger.info("STEP 3: Sending alert email …")
|
||
send_alert(
|
||
smtp_host=SMTP_HOST,
|
||
smtp_port=SMTP_PORT,
|
||
sender_email=SENDER_EMAIL,
|
||
sender_password=SENDER_PASSWORD,
|
||
recipients=RECIPIENT_EMAILS,
|
||
new_entries=new_entries,
|
||
is_first_run=is_first_run,
|
||
downloaded_csv=downloaded_csv
|
||
)
|
||
logger.info(" ✓ Email sent.")
|
||
|
||
# ── Step 4 ─ Persist the new CSV as last_export.csv ─────────────────────
|
||
logger.info(f"STEP 4: Saving export as last_export.csv → {LAST_CSV_PATH}")
|
||
shutil.copy2(downloaded_csv, str(LAST_CSV_PATH))
|
||
|
||
logger.info("=" * 65)
|
||
logger.info(" Automation complete ✓")
|
||
logger.info("=" * 65)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|