"""Pull all deleted contacts from Barbara's mailbox.""" import subprocess, json TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f" CLAUDE_APP = "fabb3421-8b34-484b-bc17-e46de9703418" CLAUDE_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO" USER = "barbara@bardach.net" # Get token token_cmd = subprocess.run([ 'curl', '-s', '-X', 'POST', f'https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token', '-d', f'client_id={CLAUDE_APP}&client_secret={CLAUDE_SECRET}&scope=https://graph.microsoft.com/.default&grant_type=client_credentials' ], capture_output=True, text=True) token = json.loads(token_cmd.stdout)['access_token'] # Page through all deleted contacts url = (f"https://graph.microsoft.com/beta/users/{USER}/contacts" f"?$filter=parentFolderId%20eq%20'deleteditems'" f"&$top=100" f"&$select=displayName,emailAddresses,companyName,lastModifiedDateTime") all_deleted = [] page = 0 while url: page += 1 cmd = ['curl', '-s', '-H', f'Authorization: Bearer {token}', url] result = subprocess.run(cmd, capture_output=True, text=True) data = json.loads(result.stdout) if 'error' in data: print(f"Error on page {page}: {data['error'].get('message','')[:200]}") break items = data.get('value', []) all_deleted.extend(items) url = data.get('@odata.nextLink') if page % 5 == 0: print(f" Page {page}: {len(all_deleted)} deleted contacts so far...") if not items: break print(f"\nTotal deleted contacts found: {len(all_deleted)}") # Analyze from collections import Counter if all_deleted: # Save full data with open('D:/ClaudeTools/temp/bardach_deleted_contacts.json', 'w') as f: json.dump(all_deleted, f, indent=2) # Name analysis names = [c.get('displayName', '').strip() for c in all_deleted if c.get('displayName')] name_counts = Counter([n.lower() for n in names]) dupes = {k: v for k, v in name_counts.items() if v > 1} print(f"\nUnique names: {len(set(n.lower() for n in names))}") print(f"Names with duplicates: {len(dupes)}") # Check overlap with active contacts active_file = 'D:/ClaudeTools/temp/bardach_contacts.json' try: with open(active_file) as f: active = json.load(f) active_names = set(c.get('displayName', '').strip().lower() for c in active if c.get('displayName')) deleted_names = set(n.lower() for n in names) overlap = active_names & deleted_names only_deleted = deleted_names - active_names print(f"\nActive contacts: {len(active_names)}") print(f"Deleted contacts (unique names): {len(deleted_names)}") print(f"Names in BOTH active and deleted: {len(overlap)}") print(f"Names ONLY in deleted (not in active): {len(only_deleted)}") if only_deleted: print(f"\nSample of contacts only in Deleted Items (not in active contacts):") for name in sorted(only_deleted)[:50]: print(f" - {name}") if len(only_deleted) > 50: print(f" ... and {len(only_deleted) - 50} more") except FileNotFoundError: print("\n(Active contacts file not found for comparison)") # Show date range dates = [c.get('lastModifiedDateTime', '')[:10] for c in all_deleted if c.get('lastModifiedDateTime')] if dates: print(f"\nDate range of deleted contacts: {min(dates)} to {max(dates)}") # Sample print(f"\nFirst 30 deleted contacts:") for c in all_deleted[:30]: name = c.get('displayName', '(no name)') emails = ', '.join([e.get('address', '') for e in c.get('emailAddresses', [])]) company = c.get('companyName', '') modified = c.get('lastModifiedDateTime', '?')[:10] detail = emails or company or '' print(f" {modified} - {name}" + (f" ({detail})" if detail else ""))