Contacts

Automating Contacts

Contact information is ubiquitous and used all the time. Therefore it is particularly important to have correct and usable data. JXA can be employed to clean up erroneous information.

Standardize phone numbers#

Before the advent of mobile phones, phone numbers were often written without prefixes for the country or even the area for numbers in the own area. Using them with mobile phones is not possible because they require the area codes. Also, calling a number at home from another country is not possible without the proper international prefix.

The following script finds all numbers without area or country prefix and standardizes them by prefixing them. Only toll-free numbers (beginning with 0800) are left as they are because one can’t call them from another country anyway ( line 10).

As it is, the script works fine for german phone numbers which are of the form (local prefix) number, the local prefix always beginning with “0”. Inversely, any number not starting with “0” must be prefixed with the country prefix and the area code (“30” in the script).

In addition to normalizing the phone numbers, the script copies e-mail addresses erroneously entered as phone number to a new e-mail entry for this person ( line 40). This part shows a bug in Contact’s JXA: theoretically, the e-mail should be created with make(), but that does not work.

// Normalize phone numbers in Contacts
(() => {
  
  const localDefault = '30'; /* CHANGE! */
  const internationalPrefix = '+';
  const internationalDefault = '+49'; /* CHANGE! */
  const internationalOld = /^00/; /* Change? */
  const localOld = /^0/; /* Change? */
  
  /* Correct phone #s begin with + or 0800 */
  const correctRE = new RegExp(/^(\+|0800)/);
  
  const app = Application("Contacts");
  const phones = app.people.phones();
  
  /* Loop over all phone number lists */
  phones.forEach((pcollection,idx) => {
    
    /* loop over every element of a phone number list */
    pcollection.forEach(p => {
      /* save the value */
      let value = p.value();
      
      /* If the # does not have the correct format */
      if (! correctRE.test(value) ) {
        
        /* If the # starts with a digit, 
        * add international and local defaults to the front */
        if (/^[1-9]/.test(value)) {
          value = internationalDefault + localDefault + value;
        }  else {
          /* Set the international prefix to + */
          value = value.replace(internationalOld, internationalPrefix);
          
          /* Replace the local prefix with the international default */
          value = value.replace(localOld, internationalDefault);
        }
        
        /* Remove slashes from the number */
        value = value.replace(/\//,"");
        if (value.indexOf('@') > 0) {
          
          /* Mail address found: 
          * add as new mail address to contact
          */
          const person = app.people[idx];
          
          /* Note: `add()` does _not_ work here */
          const newMail = app.Email({ 
            label: "Arbeit",  // CHANGE label, if needed
            value: value });
          person.emails.push(newMail);
        } else {
          /* Update the phone # */
          p.value = value;
        }
      } 
    })
  })
  /* Make sure the changes are saved */
  app.save();
})()

Save duplicates in a group#

Sometimes Contact records are duplicated by a bug in synchronization or inadvertently. The following script finds these records (using the name, company, and city to identify duplicates) and copies them to a newly created group. You can then decide if you want to remove them manually.

// Find duplicates in Contacts and save them in a group
(() => {
  app = Application("Contacts");
  /*
   * Get all people, their names, company flags and addresses
   */
  const people = app.people();
  const names = app.people.name();
  const companies = app.people.company();
  const addresses = app.people.addresses();
  
  /*
   * found: {name1 : [companyflag1, city1],
   *         name2:  [companyflag2, city2],
   *         …}
   * duplicates: Array of duplicate people entries, i.e. same   
   * name, same city, same company flag.
   */
  const found = {};
  const duplicates = [];
  /* 
   * Loop over names and put data in either found or duplicates */
  for (let i = 0; i < names.length; i++) {
    const name = names[i];
    const addr = addresses[i];
    const city = addr && addr[0] ? addr[0].city() : null; 
    const comp = companies[i];
    if (name in found 
      && found[name][1] === city 
      && found[name][0] === comp) {
    /* 
     * The current contact might be a duplicate: 
     * name, city and company flag are identical 
     * to one already found
     */
      duplicates.push(people[i]);
    } else {
      /* First occurence of this name */
      found[name] = [comp, city];
    }	
  }
  /* 
   * If there are duplicates, create a group for them
   * and move all to this group
   */
  if (duplicates.length)> 0 {
    /* 
     * Create the group, set name in the next step.
     * Using withParameters: {name : ""}
     * does not work.
     */
    const group = app.make({new: "group"});
  	group.name = "Duplicates";
    duplicates.forEach(d => app.add(d, {to: group}));
    /* 
     * save() is essential, otherwise the new group will 
     * not show up. 
     * Note that saveAdressbook() will throw an error
     */
    app.save();
  }
})()