#!/usr/bin/env python3 """Junior AE v3 - Updates CRM Temp field directly""" import json, re, time, urllib.request, urllib.error from datetime import datetime, timedelta from pathlib import Path import ssl SCRIPT_DIR = Path(__file__).parent for d in [SCRIPT_DIR / "state", SCRIPT_DIR / "logs"]: d.mkdir(parents=True, exist_ok=True) STATE_FILE = SCRIPT_DIR / "state" / "jae-v3-state.json" LOG_FILE = SCRIPT_DIR / "logs" / f"jae-v3-{datetime.now().strftime('%Y%m%d')}.log" CRM_URL = "https://salesforce.hoaledgeriq.com/rest" CRM_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5M2FmNGFmNS0zZWQ0LTQ1ZDMtOWE5Zi01MDMzZjc3YTY3MjMiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiOTNhZjRhZjUtM2VkNC00NWQzLTlhOWYtNTAzM2Y3N2E2NzIzIiwiaWF0IjoxNzczMzI4NDQzLCJleHAiOjE4MDQ3ODE2NDIsImp0aSI6IjIwZjEyYzkwLTRkMDctNGJmNi1iMzk3LTZjNmU3MzlmMThjOCJ9.zeM5NvwCSGEcz99m2LYtgb0sVD6WUXcCF7SwonFg930" def log(msg): ts = datetime.now().strftime('%H:%M:%S') print(f"[{ts}] {msg}") with open(LOG_FILE, 'a') as f: f.write(f"[{ts}] {msg}\n") def load_state(): if STATE_FILE.exists(): return json.loads(STATE_FILE.read_text()) return {"last_check": (datetime.now() - timedelta(days=7)).isoformat(), "processed": 0, "upgraded": 0} def save_state(s): STATE_FILE.write_text(json.dumps(s, indent=2)) def fetch_notes(): try: req = urllib.request.Request( f"{CRM_URL}/notes?limit=100&order[createdAt]=desc", headers={"Authorization": f"Bearer {CRM_TOKEN}", "Accept": "application/json"} ) with urllib.request.urlopen(req, timeout=15) as r: return json.loads(r.read().decode()).get('data', {}).get('notes', []) except Exception as e: log(f"Fetch error: {e}") return [] def get_temp(title): t = title.upper() if 'HOT' in t: return 'HOT' if 'WARM' in t: return 'WARM' if 'COLD' in t: return 'COLD' return None def extract_url(body): if not body: return None m = re.search(r'Site:\s*(https?://[^\s\n<]+)', str(body)) if m: return m.group(1).strip() m = re.search(r'(https?://[^\s\n<"]+)', str(body)) return m.group(1) if m else None def validate_website(url): if not url: return False, "no_url" if not url.startswith('http'): url = 'https://' + url try: ssl_context = ssl._create_unverified_context() req = urllib.request.Request( url, headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept": "text/html,*/*", } ) with urllib.request.urlopen(req, timeout=15, context=ssl_context) as r: content = r.read() code = r.getcode() if code != 200: return False, f"http_{code}" if len(content) < 500: return False, "too_small" html = content.decode('utf-8', errors='ignore')[:3000].lower() has_title = '' in html has_body = '<body' in html text = re.sub(r'<[^>]+>', '', html) has_content = len(text.strip()) > 100 if has_title and has_body and has_content: return True, "real_website" return False, f"missing_{'title' if not has_title else 'body' if not has_body else 'content'}" except urllib.error.HTTPError as e: if e.code in [301, 302]: new_url = e.headers.get('Location', '') if new_url and new_url != url: return validate_website(new_url) return False, f"http_{e.code}" except Exception as e: return False, str(e)[:30] def upgrade(temp): return {'COLD': 'WARM', 'WARM': 'HOT', 'HOT': 'HOT'}.get(temp, temp) def update_note_full(note_id, body, title, new_temp, status): """Update CRM Temp field and title""" try: # Update title to reflect new temp new_title = title for old in ['COLD', 'WARM', 'HOT']: if old in title: new_title = title.replace(old, new_temp) break # Update body with validation info new_body = body + f"\n\n**JAE v3:** {datetime.now().strftime('%Y-%m-%d %H:%M')}\n" \ f"**Temp Updated:** {new_temp}\n" \ f"**Status:** {status}\n" \ f"**__JAE_Processed__**" # CRM update - only update temp field (custom field on notes) update_data = { "title": new_title, "bodyV2": {"markdown": new_body}, "temp": new_temp } data = json.dumps(update_data).encode() req = urllib.request.Request( f"{CRM_URL}/notes/{note_id}", headers={"Authorization": f"Bearer {CRM_TOKEN}", "Content-Type": "application/json"}, data=data, method='PUT' ) with urllib.request.urlopen(req, timeout=10) as r: return True, new_title except Exception as e: log(f"Update failed: {e}") return False, title def process(): s = load_state() log("=== JAE v3 Starting - Processing ALL notes ===") notes = fetch_notes() log(f"Fetched {len(notes)} notes") for note in notes: body = note.get('bodyV2', {}).get('markdown', '') # Skip already processed by v3 if '__JAE_Processed__' in body: continue title = note.get('title', '') note_id = note.get('id') temp = get_temp(title) if not temp: log(f"Skip: no temp in title: {title[:35]}") continue url = extract_url(body) if not url: log(f"Skip: no URL: {title[:35]}") continue log(f"Validating: {url[:45]}") is_valid, status = validate_website(url) if is_valid and temp != 'HOT': new_temp = upgrade(temp) log(f"UPGRADE: {temp}->{new_temp} | {title[:40]}") ok, new_title = update_note_full(note_id, body, title, new_temp, status) if ok: s['upgraded'] += 1 log(f" Updated title: {new_title[:50]}") else: # Still process to set Temp field even if not upgrading ok, new_title = update_note_full(note_id, body, title, temp, f"verified_{status}") log(f"Verified: {temp} | {title[:40]}") s['processed'] += 1 time.sleep(0.5) # Rate limiting s['last_check'] = datetime.now().isoformat() save_state(s) log(f"=== Done: {s['processed']} processed, {s['upgraded']} upgraded ===") def main(): while True: process() log("Waiting 3 hours...") time.sleep(10800) if __name__ == "__main__": main()