Files
claudetools/temp/bardach_merge_contacts.py

148 lines
5.9 KiB
Python

import urllib.request, urllib.parse, json, os
from collections import defaultdict
APP_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f"
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
USER_ID = "41d14430-feb4-4ae2-aed6-2bd4e6384ca7"
# Get token
token_data = urllib.parse.urlencode({
'client_id': APP_ID,
'client_secret': CLIENT_SECRET,
'scope': 'https://graph.microsoft.com/.default',
'grant_type': 'client_credentials'
}).encode()
req = urllib.request.Request(f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token", data=token_data, method='POST')
with urllib.request.urlopen(req) as r:
token = json.loads(r.read())['access_token']
base = f'https://graph.microsoft.com/v1.0/users/{USER_ID}/contacts'
def patch_contact(cid, data):
body = json.dumps(data).encode()
req = urllib.request.Request(f'{base}/{cid}', data=body, method='PATCH',
headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'})
with urllib.request.urlopen(req) as r:
return r.status
def delete_contact(cid):
req = urllib.request.Request(f'{base}/{cid}', method='DELETE',
headers={'Authorization': f'Bearer {token}'})
with urllib.request.urlopen(req) as r:
return r.status
# Fetch all contacts
url = f'{base}?$select=id,displayName,emailAddresses,companyName,businessPhones,mobilePhone,jobTitle,givenName,surname&$orderby=displayName&$top=999'
all_contacts = []
while url:
req = urllib.request.Request(url, headers={'Authorization': f'Bearer {token}'})
with urllib.request.urlopen(req) as r:
data = json.loads(r.read())
all_contacts.extend(data.get('value', []))
url = data.get('@odata.nextLink')
print(f'Total contacts: {len(all_contacts)}')
by_name = defaultdict(list)
for c in all_contacts:
name = c.get('displayName', '').strip()
if name:
by_name[name].append(c)
dupes = {k: v for k, v in by_name.items() if len(v) > 1}
print(f'Duplicate groups: {len(dupes)}')
def merge_emails(keeper, donor):
keeper_emails = set(e.get('address', '').lower() for e in keeper.get('emailAddresses', []) if e.get('address', '').strip())
new_emails = [e for e in keeper.get('emailAddresses', []) if e.get('address', '').strip()]
added = []
for e in donor.get('emailAddresses', []):
addr = e.get('address', '')
if addr.strip() and addr.lower() not in keeper_emails:
new_emails.append(e)
added.append(addr)
return new_emails, added
def merge_phones(keeper, donor):
def normalize(p):
return ''.join(c for c in p if c.isdigit())[-10:]
keeper_phones = set()
for p in (keeper.get('businessPhones') or []):
keeper_phones.add(normalize(p))
if keeper.get('mobilePhone'):
keeper_phones.add(normalize(keeper['mobilePhone']))
new_phones = []
for p in (donor.get('businessPhones') or []):
if normalize(p) not in keeper_phones:
new_phones.append(p)
if donor.get('mobilePhone') and normalize(donor['mobilePhone']) not in keeper_phones:
new_phones.append(donor['mobilePhone'])
return new_phones
def do_merge(name, keeper, donor):
new_emails, added_emails = merge_emails(keeper, donor)
new_phones = merge_phones(keeper, donor)
patch = {}
if added_emails:
patch['emailAddresses'] = new_emails
if new_phones:
biz = list(keeper.get('businessPhones') or []) + new_phones
patch['businessPhones'] = biz
if not keeper.get('companyName') and donor.get('companyName'):
patch['companyName'] = donor['companyName']
if not keeper.get('jobTitle') and donor.get('jobTitle'):
patch['jobTitle'] = donor['jobTitle']
if patch:
status = patch_contact(keeper['id'], patch)
extras = []
if added_emails: extras.append(f"emails: {added_emails}")
if new_phones: extras.append(f"phones: {new_phones}")
if 'companyName' in patch: extras.append(f"company: {patch['companyName']}")
if 'jobTitle' in patch: extras.append(f"job: {patch['jobTitle']}")
print(f' [OK] {name}: merged {", ".join(extras)} (status {status})')
else:
print(f' [OK] {name}: no new data to merge')
del_status = delete_contact(donor['id'])
print(f' Deleted duplicate (status {del_status})')
# === EXACT DUPLICATES ===
print('\n--- EXACT DUPLICATES ---')
for name in ['Bardach, Mike', 'Brandon Lopez', 'Judi Carroll', 'Kelly Yang', 'Megan Carroll', 'Winter Williams']:
contacts = dupes[name]
for c in contacts[1:]:
try:
status = delete_contact(c['id'])
print(f' [OK] Deleted: {name} (status {status})')
except Exception as e:
print(f' [ERROR] {name}: {e}')
# === PATSY SABLE (3 copies) ===
print('\n--- Patsy Sable (3 copies) ---')
patsy = dupes['Patsy Sable']
patsy_personal = [c for c in patsy if any(e.get('address', '') == 'patsy@patsysable.com' for e in c.get('emailAddresses', []))]
patsy_work = [c for c in patsy if any(e.get('address', '') == 'psable@longrealty.com' for e in c.get('emailAddresses', []))]
if len(patsy_work) >= 2:
try:
status = delete_contact(patsy_work[1]['id'])
print(f' [OK] Deleted exact work dupe (status {status})')
except Exception as e:
print(f' [ERROR] work dupe: {e}')
if patsy_personal and patsy_work:
try:
do_merge('Patsy Sable', patsy_personal[0], patsy_work[0])
except Exception as e:
print(f' [ERROR] merge: {e}')
# === MERGE PAIRS ===
print('\n--- MERGE PAIRS ---')
for name in ['Barbara Bardach', 'David Rodriguez', 'Denise Newton', 'Gina Beltran',
'Jessica Bonn', 'Kayla Manley', 'Maria Anemone', 'Mark Crager',
'Paula Williams', 'Randy Bonn', 'Susan Barry']:
contacts = dupes[name]
try:
do_merge(name, contacts[0], contacts[1])
except Exception as e:
print(f' [ERROR] {name}: {e}')
print('\n=== ALL DONE ===')