oshliaer

Обновление Гугл Контактов на основе внешних данных

Этот скрипт демонстрирует, как автоматизировать создание и обновление контактов в Google Contacts, используя данные из Google Таблицы. Скрипт выполняет следующие действия:

  1. Извлекает данные из листа Google Таблицы с именем ‘Для загрузки в контакты’
  2. Проверяет наличие группы контактов ‘System Group: My Contacts’
  3. Получает все существующие контакты
  4. Для каждой строки данных:
    • Если контакт уже существует (по имени или номеру телефона) - обновляет его
    • Если контакт не существует - создает новый
  5. Добавляет примечания в таблицу о статусе операции

Для работы скрипта требуется включить Google People API в проекте Apps Script.

/* exported contactsToGoogle */
/**
 * Основная функция для обновления контактов Google из данных таблицы.
 * Данные должны быть организованы в следующем формате:
 * Колонка A: Фамилия
 * Колонка B: Имя
 * Колонка E: Номер телефона (без +)
 */
function contactsToGoogle() {
  // Получение данных из таблицы
  const sheet = SpreadsheetApp.getActive().getSheetByName('Для загрузки в контакты');
  const data = sheet.getDataRange().getValues();

  // Найти группу контактов или вернуть ошибку если группа не существует
  const labelName = 'System Group: My Contacts';
  const { contactGroups } = People.ContactGroups.list({ groupFields: 'memberCount,name', pageSize: 1000 });
  const contactGroup = contactGroups.find(({ name }) => name == labelName);
  if (!contactGroup) {
    const message = `Ярлыка (группы) с именем ${labelName} не найдено. Создайте его вручную`;
    throw new Error(message);
  }

  // Поиск всех существующих контактов с построением индексов
  // Создаем два индекса: по имени и по номеру телефона для быстрого поиска
  const contacts = {}; // индекс контактов по имени
  const phones = {};   // индекс контактов по номеру телефона
  let nextPageToken = null;

  do {
    // Получаем до 1000 контактов за один запрос с пагинацией
    const response = People.People.Connections.list('people/me', {
      pageSize: 1000,
      personFields: 'names,phoneNumbers,memberships',
      pageToken: nextPageToken || '',
    });

    // Если есть контакты, добавляем их в индексы
    if (response.connections) {
      response.connections.forEach((person) => {
        if (person.names && person.names.length > 0) {
          const name = person.names[0].displayName;
          contacts[name] = person;
          // Создаем дополнительный индекс по номеру телефона
          person.phoneNumbers?.forEach((phone) => (phones[phone.canonicalForm] = contacts[name]));
        }
      });
    }

    nextPageToken = response.nextPageToken;
  } while (nextPageToken); // Продолжаем, пока есть следующая страница результатов

  // Обрабатываем каждую строку данных (пропускаем заголовки - первую строку)
  data.slice(1).forEach((row, i) => {
    // Проверяем, что есть все необходимые данные (фамилия, имя и телефон)
    if ([row[0], row[1], row[4]].indexOf('') < 0) {
      const middleScope = {
        noteToSheet: '',  // Сообщение для примечания
        currentDate: Utilities.formatDate(new Date(), 'Europe/Moscow', 'yyyy-MM-dd hh:mm'), // Текущая дата и время
        name: '',         // Полное имя контакта
      };

      // Форматируем телефон и имя
      const phoneNumber = `+${row[4]}`;
      const fullName = `${row[1]} ${row[0]}`; // Имя Фамилия

      middleScope.name = fullName;

      // Ищем контакт по имени или телефону
      if (contacts[fullName] || phones[phoneNumber]) {
        // Обновляем существующий контакт
        const person = contacts[fullName] || phones[phoneNumber];
        const resourceName = person.resourceName;

        // Формируем объект для обновления, сохраняя существующие данные
        const updatePerson = {
          names: [
            {
              givenName: row[1],     // Имя
              familyName: row[0],    // Фамилия
              displayName: fullName, // Отображаемое имя
            },
            // Сохраняем другие имена, если они существуют
            ...(person.names?.filter((name) => name.displayName !== fullName) ?? []),
          ],
          phoneNumbers: [
            {
              value: phoneNumber,
              type: 'work',  // Устанавливаем тип "рабочий"
            },
            // Сохраняем другие телефоны, если они существуют
            ...(person.phoneNumbers?.filter((phone) => phone.canonicalForm !== phoneNumber) ?? []),
          ],
          organizations: [
            {
              name: 'TrustTaxi',
              title: 'Водитель',
            },
            // Сохраняем другие организации, если они существуют
            ...(person.organizations?.filter((organization) => organization.name !== 'TrustTaxi') ?? []),
          ],
          memberships: [
            {
              contactGroupMembership: {
                contactGroupResourceName: contactGroup.resourceName,
              },
            },
            // Сохраняем другие группы, если они существуют
            ...(person.memberships?.filter(
              (membership) => membership.contactGroupMembership.contactGroupResourceName !== contactGroup.resourceName,
            ) ?? []),
          ],
          etag: person.etag, // Требуется для обновления контакта
        };

        try {
          // Обновляем контакт используя People API
          People.People.updateContact(updatePerson, resourceName, {
            updatePersonFields: 'names,phoneNumbers,organizations,memberships',
          });
          middleScope.noteToSheet = 'Контакт обновлен';
        } catch (e) {
          console.error(`Failed to update ${fullName}: ${e.message}`);
          middleScope.noteToSheet = 'Контакт не обновлен';
        }
      } else {
        // Создаем новый контакт если не найден существующий
        const newPerson = {
          names: [
            {
              givenName: row[1],     // Имя
              familyName: row[0],    // Фамилия
              displayName: fullName, // Отображаемое имя
            },
          ],
          phoneNumbers: [
            {
              value: phoneNumber,
              type: 'work',  // Устанавливаем тип "рабочий"
            },
          ],
          organizations: [
            {
              name: 'TrustTaxi',
              title: 'Водитель',
            },
          ],
          memberships: [
            {
              contactGroupMembership: {
                contactGroupResourceName: contactGroup.resourceName,
              },
            },
          ],
        };

        try {
          // Создаем новый контакт используя People API
          People.People.createContact(newPerson);
          middleScope.noteToSheet = 'Контакт создан';
        } catch (e) {
          console.error(`Failed to create ${fullName}: ${e.message}`);
          middleScope.noteToSheet = 'Контакт не создан';
        }
      }
      
      // Добавляем примечание к ячейке в таблице с информацией о результате операции
      sheet.getRange(i + 2, 1).setNote(`${middleScope.currentDate} ${middleScope.noteToSheet} '${middleScope.name}'`);
    } else {
      // Если данные неполные, очищаем примечание
      sheet.getRange(i + 2, 1).clearNote();
    }
  });
}