Контакты
`contacts.list`, `get`, `create`, `save`, `update`, `set_audiences`, `delete`, `delete_many`, `list_people`, `set_photo`, `remove_photo`, `block`, `unblock`, `list_threads` и `activity`.
Все методы
from openemail import openemail page = openemail.contacts.list(limit=100)contact = openemail.contacts.get('[email protected]') saved = openemail.contacts.create({ 'email': '[email protected]', 'name': 'Grace Hopper', 'notes': 'Met at the compiler workshop',}) openemail.contacts.update(saved['email'], {'notes': None})openemail.contacts.set_audiences(saved['email'], { 'audienceIds': ['aud_4c1b8e2a7d9f05c36b4e8a71'],})openemail.contacts.delete(saved['email']) print(len(page['items']), page['hasMore'], contact['source'], contact['lastSeenAt'])Сначала идут те, кого видели недавно, а контакты, которым никогда не писали, стоят в конце. source равен auto, когда строка появилась потому, что участник отправил на этот адрес сообщение из редактора приложения, а это существенно иное утверждение, нежели то, что кто-то его сохранил. Почта, приходящая с адреса, ничего не записывает, и отправка через этот API тоже.
Книга принадлежит рабочему пространству, а не одному человеку, так что контакт, сохранённый любым участником, видят все участники и все ключи. create записывает source как manual и помещает контакт в аудиторию по умолчанию прямо при записи. Назовите свои списки в audienceIds, чтобы добавить его в них тем же вызовом (для этого нужна ещё и audiences:write), либо добавьте контакт позже через openemail.audiences.add_contact. set_audiences одним вызовом точно задаёт, в каких списках состоит контакт.
Адреса хранятся в нижнем регистре, а клиент кодирует переданный вами, так что [email protected] попадёт в нужную строку. Адрес и есть идентичность, поэтому update не может его изменить: перенос контакта сводится к delete и create.
Параметры: contacts.list
limitint- Сколько контактов возвращать на страницу: целое от 1 до 200, по умолчанию 50. Значение приводится к типу, так что `'100'` из строки запроса подойдёт, а значение вне диапазона даёт 422, а не обрезается до границы.
cursorstr- `nextCursor` с предыдущей страницы. Никогда не собирайте его сами: курсор, называющий контакт, которого больше нет, даёт 400 `invalid_cursor`, а значит ваше состояние постраничности устарело и обход надо начать заново без курсора.
sourceContactSource- `'manual'` выбирает контакты, которые кто-то сохранил намеренно, а `'auto'` выбирает те, что записал редактор приложения. Не указывайте, чтобы получить всю книгу.
qstr- Ищет по имени и адресу, до 200 символов. Если на первой странице ничто не совпадает точно, вместо этого возвращаются близкие написания, и следующие страницы продолжают искать так же.
Ответ: ContactResource
contacts.list возвращает Page[ContactResource], так что строки лежат в page['items'], а обход следует за page['nextCursor'], пока page['hasMore'] равно True, и list_all и iterate делают это за вас. get, create, save, update, set_audiences, set_photo и remove_photo возвращают по одному ContactDetailResource: ту же строку плюс audiences. Адресная книга не ограничена по размеру, поэтому этот маршрут листается постранично, а не возвращает список, который молча обрывается на 200.
objectLiteral['contact']- Всегда строка `contact` как в строках списка, так и в `get`.
emailstr- Адрес, приводимый к нижнему регистру при записи, так что `[email protected]` и `[email protected]` считаются одним контактом, и ключ, который принимает каждый метод контактов, поскольку идентификатор контакта наружу не выдаётся. Строки принадлежат рабочему пространству, а не участнику или ключу, который их записал, так что все участники и все ключи рабочего пространства читают и пишут одну адресную книгу.
namestr | None- `None`, когда для адреса никогда не записывалось имя. Автоматическая запись несёт имя, только если заголовок дал что-то отличное от самого адреса, и она никогда не может перезаписать имя, введённое пользователем.
sourceContactSource | str- `auto` означает, что строка появилась потому, что пользователь отправил почту на этот адрес; `manual` означает, что кто-то ввёл его вручную (существенно иное утверждение), и upsert никогда не понижает `manual` обратно до `auto`. Почта, приходящая с адреса, намеренно не создаёт строки вовсе, так что того, кто вам только писал, здесь нет; объединение остаётся открытым, потому что колонка хранит свободный текст со значением `manual` по умолчанию.
notesstr | None- Свободный текст, который кто-то написал об этом человеке в приложении или через `update`, и никогда не сгенерированный. `None`, когда никто ничего не писал, а явный `None` в `update` очищает его.
lastSeenAtstr | None- ISO-8601 UTC, обновляется каждый раз, когда участник отправляет на этот адрес из редактора приложения, и не обновляется при приходе почты с него, что ничего не записывает. `None` у контакта, сохранённого через `create`, которому никогда не писали, и такие идут в конце убывающего порядка по `lastSeenAt`, который возвращает этот маршрут.
audienceslist[ContactAudienceResource]- Только в `get`, `create`, `save`, `update`, `set_audiences`, `set_photo` и `remove_photo`, никогда в строках списка. Каждая аудитория, в которой состоит контакт, в виде словаря с `id`, `name` и `builtin`, включая аудиторию по умолчанию. `builtin` равен `default` у аудитории, к которой принадлежит каждый контакт, и `None` у созданной кем-то, поэтому ветвитесь по нему, а не по имени, которое может изменить кто угодно.
photoUrlstr | None- Где отдаётся фото контакта, или `None`, если его нет. `set_photo` его задаёт, и каждая загрузка получает новый URL.
Назначение аудиторий контакта
set_audiences(email, {'audienceIds': [...]}) одним запросом точно задаёт, в каких аудиториях состоит контакт. Контакт вступает в каждую перечисленную аудиторию, в которой его ещё нет, и выходит из всех остальных, а вызов возвращает ContactDetailResource после изменения. Требует audiences:write, потому что записывает членство, а не сам контакт, и повтор ничего не меняет.
Аудитория по умолчанию сохраняется всегда, поэтому {'audienceIds': []} оставляет контакт только в аудитории по умолчанию. Принимает до 100 идентификаторов. Идентификатор, не соответствующий ни одной аудитории этого рабочего пространства, даёт 404 audience_not_found, и ничего не меняется, а адрес, не являющийся контактом, даёт 404 contact_not_found.
Все со страницы «Контакты»
list_people перечисляет людей, которых показывает страница «Контакты» в приложении: сохранённые контакты и каждый адрес из почты, у каждого saved, threads и lastAt. list это только сохранённые контакты. Адреса из почты приходят только тогда, когда у ключа есть и threads:read, а page['seen'] сообщает, пришли ли они. sort бывает recent, name или threads, q ищет по именам, адресам и заметкам, а blocked=True оставляет тех, кого блокирует список блокировки рабочего пространства, включая правила на весь домен. blockedBy называет правило в каждой строке.
from openemail import openemail page = openemail.contacts.list_people(sort='threads', limit=50) for person in page['items']: if not person['saved'] and (person['threads'] or 0) > 5: openemail.contacts.save(person['email']) blocked = openemail.contacts.list_all_people(blocked=True)list_all_people и iterate_people проходят все страницы. Курсор непрозрачен, поэтому возвращайте nextCursor таким, каким он пришёл, с теми же sort, q и blocked.
Сохранение, удаление и фото
save(email, {'name': ..., 'notes': ...}) это «Добавить в контакты» и «Оставить в контактах»: сохраняет адрес, который ещё не контакт, оставляет записанный при отправке как сохранённый вручную и возвращает удалённый. delete это «Удалить»: убирает сохранённый контакт и скрывает адрес, чтобы редактор не записал его снова, и принимает также адрес, который встречался только в почте. wasSaved сообщает, какой это был случай. delete_many удаляет до 200 за один вызов.
from pathlib import Path from openemail import openemail openemail.contacts.save('[email protected]', {'name': 'Grace Hopper'}) photo = Path('grace.jpg').read_bytes()contact = openemail.contacts.set_photo('[email protected]', photo, content_type='image/jpeg') openemail.contacts.remove_photo('[email protected]')openemail.contacts.delete_many(['[email protected]', '[email protected]'])set_photo отправляет байты изображения как есть: PNG, JPEG, WebP или GIF до 5 МБ, вписанные в квадрат 512 пикселей. Передайте content_type=, потому что у байтов нет собственного типа: без него загрузка уходит как application/octet-stream, что отклоняется с 422 invalid_image. Адрес сначала должен быть сохранённым контактом.
Блокировка
block(email) вносит адрес в список блокировки рабочего пространства, чтобы письма с него отклонялись, отбрасывая плюс-метку, а unblock(email) снимает каждое правило, которое его блокирует. Обоим нужен settings:write, потому что они меняют список блокировки, а не контакт, и ни одному не нужно, чтобы адрес был контактом.
Когда unblock снимает правило на весь домен, removed перечисляет его с list, равным blockedDomains, и вместе с ним разблокируются все в этом домене.
Переписка и активность
list_threads(email) постранично проходит цепочки, которые адрес написал или в которых ему писали, во всех папках, а list_all_threads и iterate_threads проходят их целиком. activity(email) возвращает цифры за вкладкой «Активность» контакта: получено и отправлено по интервалам, цепочки, ждущие вашего ответа, и медианное время ответа в каждую сторону. Обоим нужен threads:read.
import time from openemail import openemail threads = openemail.contacts.list_threads('[email protected]', q='invoice') activity = openemail.contacts.activity( '[email protected]', minutes=30 * 24 * 60, grain='day', offset_minutes=time.localtime().tm_gmtoff // 60,) print(len(threads['items']), activity['totals']['waiting'])Справочник
contacts.list()Полный справочникcontacts.list_all()Полный справочникcontacts.iterate()Полный справочникcontacts.get()Полный справочникcontacts.create()Полный справочникcontacts.save()Полный справочникcontacts.update()Полный справочникcontacts.set_audiences()Полный справочникcontacts.delete()Полный справочникcontacts.delete_many()Полный справочникcontacts.list_people()Полный справочникcontacts.list_all_people()Полный справочникcontacts.iterate_people()Полный справочникcontacts.set_photo()Полный справочникcontacts.remove_photo()Полный справочникcontacts.block()Полный справочникcontacts.unblock()Полный справочникcontacts.list_threads()Полный справочникcontacts.list_all_threads()Полный справочникcontacts.iterate_threads()Полный справочникcontacts.activity()Полный справочник