#!/usr/bin/env python3 """ Bardach Contact Delete: Delete remaining Temp contacts. Treats 404 as success (contact already gone). Merges were already completed successfully (70/70). """ import json import subprocess import time import sys from datetime import datetime sys.stdout.reconfigure(line_buffering=True) sys.stderr.reconfigure(line_buffering=True) TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f" CLIENT_ID = "fabb3421-8b34-484b-bc17-e46de9703418" CLIENT_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO" SCOPE = "https://graph.microsoft.com/.default" USER = "barbara@bardach.net" BASE_URL = f"https://graph.microsoft.com/v1.0/users/{USER}/contacts" DATA_FILE = "D:/ClaudeTools/temp/bardach_temp_vs_main.json" THROTTLE_DELAY = 0.25 # slightly faster since deletes are simple def get_token(): url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token" cmd = [ "curl", "-s", "-X", "POST", url, "-H", "Content-Type: application/x-www-form-urlencoded", "-d", f"client_id={CLIENT_ID}&scope={SCOPE}&client_secret={CLIENT_SECRET}&grant_type=client_credentials" ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) data = json.loads(result.stdout) if "access_token" not in data: print(f"[ERROR] Token failed: {data}") sys.exit(1) print(f"[OK] Token acquired at {datetime.now().strftime('%H:%M:%S')}") return data["access_token"] def api_delete(token, contact_id): """DELETE a contact. Returns status code as string.""" url = f"{BASE_URL}/{contact_id}" cmd = [ "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "-X", "DELETE", url, "-H", f"Authorization: Bearer {token}" ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) return result.stdout.strip() def api_get(token, url): cmd = [ "curl", "-s", "-X", "GET", url, "-H", f"Authorization: Bearer {token}", "-H", "Content-Type: application/json" ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) return json.loads(result.stdout) # Load data with open(DATA_FILE, "r", encoding="utf-8") as f: data = json.load(f) matches = data["matches_with_extras"] exact_matches = data.get("exact_matches", []) all_temp_ids = [] for m in matches: all_temp_ids.append((m["temp_id"], m["displayName"])) for m in exact_matches: all_temp_ids.append((m["temp_id"], m["displayName"])) print(f"[INFO] Total Temp contacts to delete: {len(all_temp_ids)}") token = get_token() deleted_ok = 0 already_gone = 0 real_errors = 0 error_details = [] for i, (tid, name) in enumerate(all_temp_ids): if i > 0 and i % 500 == 0: token = get_token() if i > 0 and i % 200 == 0: print(f" [INFO] Progress {i}/{len(all_temp_ids)}: {deleted_ok} deleted, {already_gone} already gone, {real_errors} errors") code = api_delete(token, tid) time.sleep(THROTTLE_DELAY) if code in ("204", "200"): deleted_ok += 1 elif code == "404": already_gone += 1 else: real_errors += 1 if real_errors <= 10: print(f" [ERROR] {name}: HTTP {code}") error_details.append({"name": name, "code": code, "temp_id": tid}) print(f"\n[OK] Delete complete:") print(f" Deleted now: {deleted_ok}") print(f" Already gone: {already_gone}") print(f" Errors: {real_errors}") # Verification print("\n" + "=" * 70) print("VERIFICATION") print("=" * 70) token = get_token() # Check Temp folder folders_url = f"https://graph.microsoft.com/v1.0/users/{USER}/contactFolders?$filter=displayName eq 'Temp'" folders_resp = api_get(token, folders_url) time.sleep(0.5) if "value" in folders_resp and folders_resp["value"]: temp_folder_id = folders_resp["value"][0]["id"] count_url = f"https://graph.microsoft.com/v1.0/users/{USER}/contactFolders/{temp_folder_id}/contacts?$top=1&$select=displayName&$count=true" count_resp = api_get(token, count_url) remaining = len(count_resp.get("value", [])) odata_count = count_resp.get("@odata.count", "N/A") has_more = "@odata.nextLink" in count_resp print(f" Temp folder: odata.count={odata_count}, first page={remaining}, has_more={has_more}") if remaining > 0: print(f" First remaining: {count_resp['value'][0].get('displayName', '?')}") else: print(f" Temp folder: not found or empty") # Check Main contacts main_count_url = f"{BASE_URL}?$top=1&$select=displayName&$count=true" main_resp = api_get(token, main_count_url) main_odata = main_resp.get("@odata.count", "N/A") print(f" Main contacts: odata.count={main_odata}") # Save results results = { "timestamp": datetime.now().isoformat(), "merge_step": "Completed previously: 70/70 patches successful", "deletes": { "total_attempted": len(all_temp_ids), "deleted_now": deleted_ok, "already_gone": already_gone, "errors": real_errors, "error_samples": error_details[:20], }, "verification": { "temp_odata_count": str(odata_count) if 'odata_count' in dir() else "N/A", "main_odata_count": str(main_odata), } } with open("D:/ClaudeTools/temp/bardach_merge_results.json", "w", encoding="utf-8") as f: json.dump(results, f, indent=2, default=str) print(f"\n[OK] Results saved to bardach_merge_results.json")