Files
claudetools/temp/bardach_deleted_contacts2.py
Mike Swanson fa15b03180 sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00
Synced files:
- Quote wizard frontend (all components, hooks, types, config)
- API updates (config, models, routers, schemas, services)
- Client work (bg-builders, gurushow)
- Scripts (BGB Lesley termination, CIPP, Datto, migration)
- Temp files (Bardach contacts, VWP investigation, misc)
- Credentials and session logs
- Email service, PHP API, session logs

Machine: ACG-M-L5090
Timestamp: 2026-03-10 19:11:00

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:59:08 -07:00

174 lines
7.1 KiB
Python

"""Search for deleted contacts using Graph search and extended properties."""
import subprocess, json, sys
CLAUDE_APP = "fabb3421-8b34-484b-bc17-e46de9703418"
CLAUDE_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f"
USER = "barbara@bardach.net"
def curl(method, url, token, body=None):
cmd = ['curl', '-s', '-X', method, '-H', f'Authorization: Bearer {token}',
'-H', 'Content-Type: application/json']
if body:
cmd.extend(['-d', json.dumps(body)])
cmd.append(url)
result = subprocess.run(cmd, capture_output=True, text=True)
try:
return json.loads(result.stdout) if result.stdout.strip() else {}
except json.JSONDecodeError:
return {'_raw': result.stdout[:500]}
# 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']
print("[OK] Token acquired")
# Method 1: Use the search API (POST to /search/query at root level)
print("\n[METHOD 1] Graph Search API for contacts...")
search_body = {
"requests": [{
"entityTypes": ["contact"],
"query": {"queryString": "*"},
"from": 0,
"size": 25
}]
}
result = curl('POST', 'https://graph.microsoft.com/v1.0/search/query', token, search_body)
if 'error' in result:
print(f" {result['error'].get('code')}: {result['error'].get('message','')[:300]}")
elif result.get('value'):
for resp in result['value']:
for hc in resp.get('hitsContainers', []):
total = hc.get('total', 0)
more = hc.get('moreResultsAvailable', False)
print(f" Found {total} contacts (more available: {more})")
else:
print(f" Response: {json.dumps(result)[:300]}")
# Method 2: Enumerate Deleted Items looking for items with specific properties
# The Graph messages endpoint in deleted items will include contact items in some cases
# Let's get the deleted items folder children (subfolders) that might be contacts
print("\n[METHOD 2] Deleted Items sub-folders...")
result2 = curl('GET',
f'https://graph.microsoft.com/v1.0/users/{USER}/mailFolders/deleteditems/childFolders?$top=50&$select=displayName,totalItemCount,childFolderCount',
token)
if result2.get('value'):
for f in result2['value']:
print(f" {f.get('displayName','?')}: {f.get('totalItemCount','?')} items")
elif 'error' in result2:
print(f" {result2['error'].get('code')}: {result2['error'].get('message','')[:200]}")
# Method 3: Use beta to get contacts with explicit parentFolderId
# Actually, let's just scan all items in deleted items to count contacts vs mail
print("\n[METHOD 3] Scanning Deleted Items for item types...")
# Get count of items in deleted items
folder_info = curl('GET',
f'https://graph.microsoft.com/v1.0/users/{USER}/mailFolders/deleteditems?$select=totalItemCount,childFolderCount',
token)
total = folder_info.get('totalItemCount', 0)
print(f" Total items in Deleted Items: {total}")
print(f" Child folders: {folder_info.get('childFolderCount', 0)}")
# Method 4: Use the beta endpoint to get items with extended properties
# This lets us see the PR_MESSAGE_CLASS to identify contacts
print("\n[METHOD 4] Sampling Deleted Items with message class property...")
sample_url = (
f"https://graph.microsoft.com/beta/users/{USER}/mailFolders/deleteditems/messages"
f"?$top=100&$select=subject,lastModifiedDateTime"
f"&$expand=singleValueExtendedProperties($filter=id%20eq%20'String%200x001A')"
f"&$orderby=lastModifiedDateTime%20desc"
)
result4 = curl('GET', sample_url, token)
if result4.get('value'):
items = result4['value']
contact_count = 0
mail_count = 0
other_count = 0
contact_names = []
for item in items:
props = item.get('singleValueExtendedProperties', [])
msg_class = None
for p in props:
if p.get('id') == 'String 0x001A':
msg_class = p.get('value', '')
break
if msg_class and 'IPM.Contact' in msg_class:
contact_count += 1
contact_names.append(item.get('subject', '(no name)'))
elif msg_class and 'IPM.Note' in msg_class:
mail_count += 1
else:
other_count += 1
print(f" In sample of {len(items)} most recent deleted items:")
print(f" Contacts (IPM.Contact): {contact_count}")
print(f" Mail (IPM.Note): {mail_count}")
print(f" Other: {other_count}")
if contact_names:
print(f"\n Deleted contact names found:")
for name in contact_names[:20]:
print(f" - {name}")
# If we found contacts, let's page through ALL deleted items to count total contacts
if contact_count > 0:
print(f"\n[STEP 5] Full scan of Deleted Items for contacts...")
all_contacts = []
all_contact_names = []
scan_url = (
f"https://graph.microsoft.com/beta/users/{USER}/mailFolders/deleteditems/messages"
f"?$top=200&$select=subject,lastModifiedDateTime"
f"&$expand=singleValueExtendedProperties($filter=id%20eq%20'String%200x001A')"
)
page = 0
total_scanned = 0
while scan_url and page < 200: # Safety limit
page += 1
data = curl('GET', scan_url, token)
if 'error' in data:
print(f" Error on page {page}: {data['error'].get('message','')[:200]}")
break
items = data.get('value', [])
if not items:
break
total_scanned += len(items)
for item in items:
props = item.get('singleValueExtendedProperties', [])
for p in props:
if p.get('id') == 'String 0x001A' and 'IPM.Contact' in p.get('value', ''):
all_contacts.append(item)
all_contact_names.append(item.get('subject', '(no name)'))
break
scan_url = data.get('@odata.nextLink')
if page % 10 == 0:
print(f" Page {page}: scanned {total_scanned}, found {len(all_contacts)} contacts...")
print(f"\n[FINAL RESULT]")
print(f" Total items scanned: {total_scanned}")
print(f" Deleted contacts found: {len(all_contacts)}")
if all_contact_names:
# Save full list
with open('D:/ClaudeTools/temp/bardach_deleted_contacts.json', 'w') as f:
json.dump(all_contacts, f, indent=2)
print(f" Full list saved to D:/ClaudeTools/temp/bardach_deleted_contacts.json")
print(f"\n Sample of deleted contact names:")
for name in all_contact_names[:30]:
print(f" - {name}")
if len(all_contact_names) > 30:
print(f" ... and {len(all_contact_names) - 30} more")
elif 'error' in result4:
print(f" {result4['error'].get('code')}: {result4['error'].get('message','')[:300]}")
else:
print(f" Empty response")