Контакты
`contacts.list`, `get`, `create`, `save`, `update`, `set_audiences`, `delete`, `delete_many`, `list_people`, `set_photo`, `remove_photo`, `block`, `unblock`, `list_threads` и `activity`.
Все методы
page = client.contacts.list(limit: 100)contact = client.contacts.get("[email protected]") saved = client.contacts.create( email: "[email protected]", name: "Grace Hopper", notes: "Met at the compiler workshop") client.contacts.update("[email protected]", notes: nil)client.contacts.set_audiences("[email protected]", audienceIds: ["aud_4c1b8e2a7d9f05c36b4e8a71"])client.contacts.delete("[email protected]") puts page.items.size, page.has_more?, contact[:source], contact[:lastSeenAt], saved[:source]list возвращает сначала контакты, замеченные недавно, а контакты, которым никогда не писали, в конце. source равен auto, когда строка записана потому, что участник отправил на этот адрес сообщение из композера приложения, а это существенно иное утверждение, чем то, что кто-то его сохранил. Почта, приходящая с адреса, ничего не записывает, как и отправка через этот API.
Адресная книга принадлежит рабочему пространству, а не одному человеку, поэтому контакт, сохранённый любым участником, является тем же контактом, который видят все участники и все ключи. create записывает source как manual и сразу при записи помещает контакт в аудиторию по умолчанию. Назовите собственные списки в audienceIds, чтобы добавить контакт в них тем же вызовом (для этого также нужна audiences:write), или добавьте контакт позже через audiences.add_contact, описанный на странице об аудиториях. set_audiences одним вызовом точно задаёт, в каких списках состоит контакт.
Адреса хранятся в нижнем регистре, а гем кодирует переданный вами адрес, поэтому [email protected] попадает в нужную строку. nil или пустой адрес выбрасывает ArgumentError ещё до отправки. Адрес является идентичностью контакта, поэтому update не может его изменить: перенос контакта означает delete и create.
Параметры: contacts.list
limitInteger- Сколько контактов возвращать на странице: целое число от 1 до 200, по умолчанию 50. Значение приводится к числу, поэтому String вроде `"100"`, прочитанная из строки запроса, подойдёт, а значение вне диапазона даёт 422, а не подрезается.
cursorString- `next_cursor` предыдущей страницы. Никогда не составляйте его сами: курсор, который называет уже не существующий контакт, даёт 400 `invalid_cursor`, выбрасываемый как `OpenEmail::InvalidRequestError`. Это означает, что ваше состояние постраничного обхода устарело и обход нужно начать заново без курсора.
sourceString- `manual` для контактов, которые кто-то сохранил намеренно, `auto` для тех, что записал композер приложения. Не указывайте, чтобы получить всю книгу.
qString- Ищет по имени и адресу, до 200 символов. Если на первой странице ничто не совпадает точно, вместо этого возвращаются близкие написания, и следующие страницы продолжают искать так же.
Ответ: контакт
contacts.list возвращает OpenEmail::Page, поэтому строки лежат в page.items, а обход идёт по page.next_cursor, пока page.has_more? истинно. list_all возвращает все строки одним Array, а iterate передаёт их по одной. get, create, update, save и set_audiences возвращают по одному контакту как Hash с ключами типа Symbol: та же строка плюс audiences. Адресная книга не ограничена по размеру, поэтому этот маршрут отдаёт данные постранично, а не возвращает Array, который молча обрывается на 200.
objectString- Всегда строка `contact` как в строках списка, так и в `get`.
emailString- Адрес, приводимый к нижнему регистру при записи, так что `[email protected]` и `[email protected]` считаются одним контактом, и ключ, который принимает каждый метод контактов, поскольку идентификатор контакта наружу не выдаётся. Строки принадлежат рабочему пространству, а не участнику или ключу, который их записал, так что все участники и все ключи рабочего пространства читают и пишут одну адресную книгу.
nameString or nil- Отображаемое имя или nil, если для адреса никогда не записывалось имя. Автоматическая запись несёт имя, только когда заголовок содержал что-то кроме самого адреса, и она никогда не может перезаписать имя, которое ввёл пользователь.
sourceString- `auto` означает, что строка записана, потому что пользователь отправил письмо на этот адрес. `manual` означает, что кто-то ввёл его вручную, а это существенно иное утверждение, и upsert никогда не понижает `manual` обратно до `auto`. Почта, приходящая с адреса, намеренно не записывает никакой строки, поэтому того, кто только писал вам, здесь нет. Считайте значение открытой String, потому что столбец является произвольным текстом со значением по умолчанию `manual`.
notesString or nil- Произвольный текст, который кто-то написал об этом человеке в приложении или через `update`, никогда не генерируется. nil, если никто ничего не написал, а `notes: nil` в `update` его очищает.
lastSeenAtString or nil- Строка ISO 8601 в UTC, которая обновляется каждый раз, когда участник отправляет на этот адрес письмо из композера приложения, а не когда с него приходит почта (это ничего не записывает). nil у контакта, сохранённого через `create`, которому никогда не писали, и такие контакты идут последними в порядке убывания `lastSeenAt`, в котором отвечает этот маршрут.
audiencesArray<Hash>- Только в `get`, `create`, `update`, `save` и `set_audiences`, никогда в строках списка. Все аудитории, в которых состоит контакт, включая аудиторию по умолчанию, в виде Hash с `id`, `name` и `builtin`. `builtin` равен `default` у аудитории, к которой принадлежит каждый контакт, и nil у созданной кем-то, поэтому ветвитесь по нему, а не по имени, которое может изменить кто угодно.
photoUrlString or nil- Откуда отдаётся фото контакта, или nil, если фото нет. `set_photo` задаёт его, и каждая загрузка получает новый URL.
Задание аудиторий контакта
set_audiences(email, audienceIds: [...]) одним запросом точно задаёт, в каких аудиториях состоит один контакт. Контакт вступает во все перечисленные аудитории, в которых ещё не состоит, и выходит из всех остальных, а вызов возвращает контакт после изменения вместе с его audiences. Ему нужна audiences:write, потому что он записывает членство, а не сам контакт, и повтор ничего не меняет, поэтому гем повторяет его после сетевого сбоя.
Аудитория по умолчанию сохраняется всегда, поэтому audienceIds: [] оставляет контакт только в аудитории по умолчанию. Принимает до 100 идентификаторов. Идентификатор, который не называет ни одной аудитории этого рабочего пространства, даёт 404 audience_not_found, и ничего не меняется, а адрес, который не является контактом, даёт 404 contact_not_found. Оба выбрасывают OpenEmail::NotFoundError.
Все со страницы «Контакты»
list_people перечисляет людей, которых показывает страница «Контакты» в приложении: сохранённые контакты и каждый адрес из почты, у каждого saved, threads и lastAt, и возвращает OpenEmail::PeoplePage, которая добавляет seen к items, has_more? и next_cursor. list возвращает только сохранённые контакты. Адреса из почты приходят, только если у ключа есть ещё и threads:read, а page.seen сообщает, пришли ли они. sort: принимает recent, name или threads, и OpenEmail::PEOPLE_SORTS их перечисляет. q: ищет по именам, адресам и заметкам, а blocked: true оставляет людей, которых блокирует список блокировки рабочего пространства, включая правила на целый домен. blockedBy называет правило в каждой строке.
page = client.contacts.list_people(sort: "threads", limit: 50) page.items.each do |person| client.contacts.save(person[:email]) if !person[:saved] && person[:threads].to_i > 5end blocked = client.contacts.list_all_people(blocked: true)puts page.seen, blocked.sizelist_all_people возвращает все страницы одним Array, а iterate_people передаёт каждого человека в блок или без блока возвращает Enumerator. Ни один из них не сообщает seen, поэтому, чтобы узнать его, прочитайте одну страницу через list_people. Курсор непрозрачен, поэтому передавайте next_cursor обратно как cursor: ровно в том виде, в каком он пришёл, с теми же sort:, q: и blocked:.
Сохранение, удаление и фото
save(email) с необязательными name: и notes: соответствует действиям «Добавить в контакты» и «Оставить в контактах»: сохраняет адрес, который ещё не является контактом, оставляет записанный при отправке как сохранённый вручную и возвращает удалённый. delete соответствует действию «Удалить»: убирает сохранённый контакт и скрывает адрес, чтобы композер не записал его снова, и принимает также адрес, который встречался только в почте. wasSaved в возвращаемом Hash сообщает, какой это был случай. delete_many удаляет до 200 за один вызов.
client.contacts.save("[email protected]", name: "Grace Hopper") contact = client.contacts.set_photo("[email protected]", File.binread("photo.jpg"), content_type: "image/jpeg")puts contact[:photoUrl] client.contacts.set_photo("[email protected]", Pathname("photo.png")) client.contacts.remove_photo("[email protected]")client.contacts.delete_many(["[email protected]", "[email protected]"])set_photo отправляет байты изображения как есть: PNG, JPEG, WebP или GIF до 5 МБ, вписанные в квадрат 512 пикселей. Байты передаются как двоичная String, IO или Pathname. Передайте content_type: или байты, которые несут собственный тип: объект, отвечающий на content_type, например загрузку Rails, или File либо Pathname, имя которого заканчивается на .png, .jpg, .jpeg, .webp или .gif. Без типа байты уходят как application/octet-stream, и сервер отклоняет их с 422 invalid_image. OpenEmail::CONTACT_PHOTO_TYPES перечисляет четыре типа. Адрес сначала должен стать сохранённым контактом.
Блокировка
block(email) вносит адрес в список блокировки рабочего пространства, чтобы письма с него отклонялись, отбрасывая плюс-метку, а unblock(email) снимает каждое правило, которое его блокирует. Обоим нужен settings:write, потому что они меняют список блокировки, а не контакт, и ни одному не нужно, чтобы адрес был контактом.
Когда unblock снимает правило на целый домен, removed перечисляет его с list, равным blockedDomains, и вместе с ним разблокируются все адреса этого домена. OpenEmail::CONTACT_BLOCK_LISTS называет оба списка.
Переписка и активность
list_threads(email) постранично перебирает цепочки во всех папках, в которых этот адрес писал или ему писали, а list_all_threads и iterate_threads обходят их. activity(email) возвращает числа, стоящие за вкладкой «Активность» контакта: полученные и отправленные по интервалам, цепочки, ждущие вашего ответа, и медианное время ответа в каждую сторону. Обоим нужна threads:read.
threads = client.contacts.list_threads("[email protected]", q: "invoice") activity = client.contacts.activity( "[email protected]", minutes: 30 * 24 * 60, grain: "day", offset_minutes: Time.now.utc_offset / 60) puts threads.items.size, activity.dig(:totals, :waiting)activity принимает именованные аргументы в snake_case. minutes: задаёт окно, которое без него составляет 90 дней. grain: задаёт ширину интервала: minute, hour или day. offset_minutes: задаёт, на сколько минут к востоку от UTC проходит граница суток. Time.now.utc_offset / 60 даёт локальное смещение, и гем отправляет его как offsetMinutes из API.