Plank help · updated 2026-09-17

Подключение Google (Gmail, Calendar, Drive)

Подключите аккаунт Google, чтобы ассистент мог читать и отправлять почту, а также работать с Calendar, Drive, Docs и Sheets — с точной одноразовой настройкой, которая срабатывает с первого раза.

Agents: fetch the raw markdown of this page at /ru/help/connecting-google.md

Подключение Google

Ваш ассистент может работать с вашим аккаунтом Google — читать и отправлять письма в Gmail, вести Calendar и работать с Drive, Docs и Sheets. После подключения вы просто просите («есть новые письма от этого клиента?», «отправь им такой ответ», «добавь заказ в таблицу учёта») — и ассистент это делает.

Вход в Google использует OAuth — вы один раз разрешаете доступ в собственном браузере, а ассистент хранит токен, чтобы не спрашивать снова. На этой странице описана одноразовая настройка. Она написана и для вас, и для ассистента: ассистент загружает её по адресу https://plank.md/help/connecting-google.md перед подключением Google, чтобы соединение прошло чисто с первого раза, а не прервалось на полпути.

Зачем такая аккуратная настройка? Во входе в Google есть несколько движущихся частей, которые должны точно совпасть — правильный тип клиента OAuth, нужные разрешения и токен, включающий офлайн-«обновление», чтобы он продолжал работать. Ошибётесь в одном — и соединение сработает один раз, а потом сломается, или не завершится вовсе. Шаги ниже — это та самая комбинация, которая работает.

Шаг 0 — Сначала попробуйте общее подключение Plank (ассистент)

Прежде чем отправлять кого-либо в Google Cloud Console, проверьте, может ли Plank выдать готовый клиент OAuth. Это быстрый путь — большинство подключений должны заканчиваться именно на Шаге 0, даже не доходя до Шага 1.

Установите набор инструментов (пропустите, если scripts/google/ уже существует):

BASE=https://plank.md/help/kits
for KIT in $(curl -fsS "$BASE/google-base/manifest.json" | jq -r '.installOrder[]'); do
  curl -fsS "$BASE/$KIT/manifest.json" | jq -r '.files[].path' | while read -r p; do
    mkdir -p "$(dirname "$p")"
    curl -fsS "$BASE/$KIT/files/$p" -o "$p"
  done
done

Запросите общий клиент. $PLANK_API_URL и $PLANK_WORKSPACE_ID уже есть в окружении.

mkdir -p scripts/google
attempt=1
while [ "$attempt" -le 3 ]; do
  code=$(curl -s -o /tmp/gclient.json -w '%{http_code}' \
    "$PLANK_API_URL/internal/google/oauth-client?workspaceId=$PLANK_WORKSPACE_ID")
  case "$code" in
    503) [ "$attempt" -lt 3 ] && sleep 3 ;;
    429) [ "$attempt" -lt 3 ] && sleep 15 ;;
    *) break ;;
  esac
  attempt=$((attempt + 1))
done
echo "http $code"

Записывайте ответ в файл, никогда в stdout — в нём client secret, и он не должен попасть в переписку. Используйте -w, а не -f: -f отбрасывает тело ошибки, а вам нужно прочитать reason, чтобы выбрать правильную ветку.

Есть четыре исхода, и они не взаимозаменяемы:

  • 200 — клиент доступен. Переместите файл на место и переходите сразу к Шагу 5 — Авторизация ниже; Шаги 1-4 не нужны.

    mv /tmp/gclient.json scripts/google/credentials.json
    
  • 403окончательный отказ. Тело ответа: {"reason": "cap" | "disabled", "setupUrl": "..."} — либо лимит общего проекта Google почти исчерпан, либо переключатель на платформе выключен. В любом случае общее подключение Plank сейчас действительно недоступно — продолжайте с Шага 1 ниже и создавайте собственный проект Google Cloud пользователя. Это тот единственный случай, ради которого существует ручная инструкция.

  • 503 — эндпоинт не смог определить ответ (кратковременно не удалось прочитать базу данных), а не отказал. Тело ответа: {"reason": "unknown"} — намеренно без setupUrl, потому что ручная инструкция — неправильный ответ на «мы не знаем», а не правильный. Цикл выше уже делает несколько повторных попыток, прежде чем сдаться; большинство 503 проходят за несколько секунд. Переходите к Шагу 1 только если после повторов всё ещё 503 — и даже тогда прямо скажите пользователю, что проверка общего подключения временно недоступна, а не подавайте это как «общего подключения не существует».

  • 429 — эндпоинт ограничивает частоту запросов, это не отказ. Обращайтесь с ним так же, как с 503: можно повторить, это не повод переходить к Шагу 1. Это не гипотетический случай — лимит привязан к адресу хоста песочницы, вызывающего эндпоинт, а его делят все контейнеры на этом хосте, так что всплеск активности чужого контейнера может ограничить и ваш, хотя вы ничего не нарушали. Цикл выше уже ждёт между повторами дольше для 429, чем для 503 (окно ограничения — целая минута, а не разовый сбой), и тело ответа здесь читать не за чем. Переходите к Шагу 1 только если после повторов всё ещё 429 — и скажите пользователю, что общее подключение временно перегружено, а не что его не существует.

Ошибка в этой развилке отправляет пользователя в десятиминутный обход через Cloud Console из-за того, что могло быть односекундным сбоем. Если сомневаетесь — сначала повторите попытку, а уже потом переходите дальше.

Что понадобится

(Только если Шаг 0 не сработал — 403 с reason: "cap" или "disabled", либо стойкий 503/429 после повторов.)

Аккаунт Google и около десяти минут в Google Cloud Console (бесплатно). Эту часть вы делаете сами, потому что она привязана к вашему собственному аккаунту Google; ассистент не может кликать за вас по экранам согласия Google. После того как вы пройдёте это один раз, повторять не придётся.

Шаг 1 — Создайте проект Google Cloud и включите API

  1. Откройте console.cloud.google.com, войдя под тем аккаунтом Google, который хотите подключить.
  2. Создайте новый проект (например, «Plank Tools»).
  3. Перейдите в APIs & Services → Library и включите (Enable) эти API. По умолчанию включите все — тогда ассистент сможет работать с любым из этих инструментов позже, не отправляя вас сюда снова:
    • Gmail API — чтение, упорядочивание и отправка почты
    • Google Calendar API — события и расписание
    • Google Drive API — файлы и папки
    • Google Docs API — документы
    • Google Sheets API — таблицы
    • Google Slides API — презентации
    • Google Tasks API — задачи
    • Нужен другой сервис Google (Contacts, Forms, Apps Script, …)? Включите и его API — просто найдите по названию в Library.

Шаг 2 — Настройте экран согласия OAuth

  1. Перейдите в APIs & Services → OAuth consent screen (в новой консоли это Google Auth Platform → Branding / Audience).
  2. Личный аккаунт @gmail.com: выберите External. (Вариант Internal существует только для аккаунтов организаций Google Workspace.) Рабочий аккаунт / Google Workspace (например, name@yourcompany.com): выберите Internal — это избавит от возни с тест-пользователями и проверкой.
  3. Заполните название приложения, вашу почту поддержки и контактную почту разработчика.
  4. Добавьте разрешения (scopes). По умолчанию запросите полный набор ниже, чтобы ассистент мог работать со всеми сервисами Google и не заставлял вас переподключаться, когда вы позже попросите что-то новое:
    • https://www.googleapis.com/auth/gmail.modify и .../gmail.send (чтение, упорядочивание и отправка почты)
    • https://www.googleapis.com/auth/calendar (Calendar)
    • https://www.googleapis.com/auth/drive (Drive)
    • https://www.googleapis.com/auth/documents (Docs)
    • https://www.googleapis.com/auth/spreadsheets (Sheets)
    • https://www.googleapis.com/auth/presentations (Slides)
    • https://www.googleapis.com/auth/tasks (Tasks)
    • Просили ассистента про другой сервис Google? Добавьте сюда и его scope.
  5. Аккаунт External: в разделе Test users добавьте адрес Google, который будете подключать. (На следующем шаге вы выведете приложение из режима Testing, так что это просто чтобы вход работал пока что.)

Шаг 3 — Опубликуйте приложение в Production (не пропускайте)

Именно этот шаг сохраняет соединение живым. На экране согласия OAuth (новая консоль: Google Auth Platform → Audience) установите Publishing status в In production — нажмите Publish app, затем Confirm.

Почему это обязательно: пока приложение остаётся в режиме Testing, Google аннулирует его refresh-токен примерно каждые 7 дней — поэтому ассистент «разлогинивается», и вам приходится авторизоваться снова и снова. В Production токен продолжает работать, и вы входите всего один раз.

Завершать проверку (verification) Google для личного использования не нужно. Поскольку полный набор включает ограниченные (restricted) scopes (Gmail), Google покажет здесь жёлтый баннер «requires verification» и экран «unverified app» при входе — для приложения, которым пользуетесь только вы, это нормально; просто продолжите. Проверка лишь убирает это предупреждение и снимает лимит в 100 пользователей; на то, останется ли токен живым, она не влияет.

Шаг 4 — Создайте клиент OAuth

  1. Перейдите в APIs & Services → Credentials → Create Credentials → OAuth client ID.
  2. Тип приложения: Desktop app. (Это важно — клиент типа Desktop app использует редирект http://localhost, на котором основан описанный ниже способ со вставкой ссылки. Не используйте клиент «Web application», если не знаете его redirect URIs.)
  3. Скачайте JSON и передайте его ассистенту (вставьте в чат или загрузите файл). Ассистент сохранит его в рабочем пространстве как scripts/google/credentials.json.

Шаг 5 — Авторизация (часть, которую вы делаете в своём браузере)

Поскольку ваш ассистент работает на удалённой машине без собственного браузера, вы разрешаете доступ в своём браузере и возвращаете результат. Ассистент ведёт вас по этому процессу — вот что вы увидите:

  1. Ассистент даёт вам ссылку для входа. Откройте её в своём браузере и разрешите доступ.
  2. Google покажет «Google hasn't verified this app» — для вашего собственного приложения это нормально. Нажмите Advanced → Go to (название вашего приложения) и продолжите.
  3. После того как вы разрешите доступ, Google перенаправит вас на адрес http://localhost…, который не загрузится — это тоже нормально. Скопируйте полную ссылку (URL) из адресной строки браузера (в ней содержится одноразовый код).
  4. Вставьте эту ссылку целиком обратно в чат. Ассистент обменяет её на токен, сохранит в scripts/google/token.json и подтвердит, прочитав профиль вашего аккаунта.

Вот и всё — Google подключён, а инструменты Google ассистента появляются в боковой панели Интеграции.

Работа с Таблицами — для ассистента

В наборе уже есть ридер — scripts/google/sheets/read-sheet.py. Проверьте scripts/google/sheets/, прежде чем писать что-то своё. Чтение таблицы нужно почти любой реальной задаче — построить дашборд, вытащить цифры в отчёт, сверить с другой системой, — поэтому используйте его напрямую, а не изобретайте одноразовый ридер под каждую задачу; раньше, пока ридер вообще не входил в набор, это было главным источником потерянных ходов в работе с Таблицами.

Сначала прочитайте Запуск команд на машине. Два факта оттуда определяют, запустятся ли эти скрипты вообще: интерпретатор — python3 (никогда python), а библиотеки Google лежат в пользовательском site-packages, поэтому PYTHONNOUSERSITE=1 ломает здесь любой импорт. Если при запуске печатается FutureWarning про версию Python — добавьте префикс PYTHONWARNINGS=ignore и работайте дальше.

read-sheet.py импортирует общий помощник scripts/google/lib/google_auth.py (он уже лежит рядом с credentials.json и token.json), поэтому обновление токена достаётся бесплатно. Скрипта записи в наборе пока нет — напишите scripts/google/sheets/update-sheet.py сами, один раз, по образцу ниже, когда задаче впервые понадобится записать ячейки.

scripts/google/sheets/read-sheet.py — уже установлен

Принимает полный URL таблицы или просто ID. Без указания диапазона возвращает структуру книги — заголовок, gid и размер каждой вкладки. Именно это и нужно на первом вызове, чтобы дальше адресовать вкладки точно. Флаги: --range (диапазон A1), --gid (чтение по id вкладки — имеет приоритет над --range), --formulas (вернуть формулы вместо вычисленных значений).

scripts/google/sheets/update-sheet.py — не входит в набор, напишите сами

#!/usr/bin/env python3
# @plank-integration
# provider: google
# service: sheets
# name: Update Google Sheet
# description: Writes values or formulas to a specified Google Sheets range.
# requires:
#   - file: scripts/google/credentials.json
#   - file: scripts/google/token.json

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "lib"))
from google_auth import google_service  # noqa: E402


def spreadsheet_id(value: str) -> str:
    match = re.search(r"/spreadsheets/d/([^/]+)", value)
    return match.group(1) if match else value


def main() -> None:
    parser = argparse.ArgumentParser(description="Update cells in Google Sheets.")
    parser.add_argument("spreadsheet", help="Spreadsheet URL or ID.")
    parser.add_argument("--range", dest="cell_range", required=True, help="A1 range, e.g. 'Sheet1'!C13:N13.")
    parser.add_argument("--values-json", required=True, help="Two-dimensional JSON array of values.")
    args = parser.parse_args()

    try:
        values = json.loads(args.values_json)
    except json.JSONDecodeError as exc:
        raise SystemExit(f"--values-json is not valid JSON: {exc}")
    if not isinstance(values, list) or any(not isinstance(row, list) for row in values):
        raise SystemExit("--values-json must be a two-dimensional JSON array")

    result = google_service("sheets", "v4").spreadsheets().values().update(
        spreadsheetId=spreadsheet_id(args.spreadsheet),
        range=args.cell_range,
        valueInputOption="USER_ENTERED",
        body={"values": values},
    ).execute()
    print(json.dumps({
        "updatedRange": result.get("updatedRange"),
        "updatedRows": result.get("updatedRows", 0),
        "updatedCells": result.get("updatedCells", 0),
    }, ensure_ascii=False))


if __name__ == "__main__":
    main()

Использование

# 1. Сначала структура — все вкладки, их gid и размер
PYTHONWARNINGS=ignore python3 scripts/google/sheets/read-sheet.py "<url-or-id>"

# 2. Значения одной вкладки (имена с пробелами или кириллицей — в кавычках)
PYTHONWARNINGS=ignore python3 scripts/google/sheets/read-sheet.py "<id>" --range "'P&L'!A1:O40"

# 3. По gid, когда имя вкладки неудобно экранировать
PYTHONWARNINGS=ignore python3 scripts/google/sheets/read-sheet.py "<id>" --gid 1234567890

# 4. Формулы вместо вычисленных значений — когда разбираетесь, как устроена модель
PYTHONWARNINGS=ignore python3 scripts/google/sheets/read-sheet.py "<id>" --range "'P&L'!A1:O40" --formulas

# 5. Запись обратно
PYTHONWARNINGS=ignore python3 scripts/google/sheets/update-sheet.py "<id>" \
  --range "'P&L'!C13:N13" --values-json '[[1,2,3]]'

Построение дашборда по таблице

Не перечитывайте таблицу на каждую правку страницы. Прочитайте один раз, сохраните, дальше итерируйте локально:

  1. Прочитайте структуру, затем те вкладки, которые действительно нужны. Уточните у пользователя, какие вкладки важны, вместо того чтобы тянуть все подряд.
  2. Запишите значения в один JSON-файл в рабочем пространстве, например finances/<имя>-data.json. Это источник данных дашборда и то, что вы перегенерируете, когда цифры меняются.
  3. Напишите скрипт синхронизацииscripts/google/sheets/sync-<имя>.py, — который пересобирает этот JSON одной командой. Дайте ему заголовок @plank-integration, чтобы пользователь мог перезапустить его из боковой панели при изменении таблицы. Именно это превращает «пересобери дашборд» из разговора в одно нажатие.
  4. Стройте HTML по JSON, следуя тому, как Plank выдаёт готовые документы. Дорабатывайте страницу вообще без обращений к сети.

Правило простое: один проход по сети, много проходов по локальной копии. Чтение из Таблиц быстрое, но каждое — это round-trip ценой в ход, а дашборду обычно нужен десяток правок уже после того, как данные получены.

Чтобы соединение не отвалилось

  • Токен обновляется сам. Настройка запрашивает офлайн-доступ, поэтому сохранённый токен включает refresh token, и ассистент обновляет его автоматически — для обычной работы повторно входить не придётся.
  • Если вас всё же просят переподключиться каждые несколько дней — приложение вернулось в Testing. Снова откройте Google Auth Platform → Audience и убедитесь, что Publishing status = In production (Шаг 3). Именно эта настройка прекращает еженедельный «разлогин».
  • Но если другие файлы токенов на том же OAuth-клиенте продолжают обновляться, дело не в сроке Testing. Статус публикации действует на весь клиент сразу. В рабочем пространстве может лежать безымянный token.json рядом с файлами token-<email>.json, и неделями отказывать с invalid_grant может только безымянный, пока именные обновляются каждый день. Переподключите этот один файл (или переведите скрипт на токен именного аккаунта) — не отправляйте пользователя менять статус публикации.

Уже подключено? Не проходите настройку заново

Эта страница — первоначальная настройка. Если Google в этом рабочем пространстве уже работал, ничего из неё повторять не нужно — и отсутствие файла там, где вы его ждали, не повод начинать сначала.

Прочитайте это, прежде чем сказать пользователю, что Google отключён:

  • Ищите токен там, откуда его читают сами скрипты. В старом рабочем пространстве он может лежать за пределами scripts/ — обычно /home/coder/.config/google/<workspace>/token.json. Путь указан в заголовке requires: и в описании самого скрипта; авторитет — они, а не соглашение с этой страницы. «Нет в scripts/google/» ≠ «отключено».
  • Токен в старом расположении просто используется там, где лежит — тот же аккаунт Google, без повторной авторизации. Не переносите его в scripts/google/ по своей инициативе: этот каталог доступен всем участникам рабочего пространства, а /home/coder приватен для одного пользователя. См. Где хранятся учётные данные.
  • Сначала проверьте, потом делайте вывод. Один недорогой запрос только на чтение (profile или чтение одной строки — но не отправка) покажет правду. Только отказ на уровне учётных данныхinvalid_grant, отозванный доступ или 401, который остаётся после обновления токена — означает, что подключение действительно потеряно, и обычная причина этого — статус Testing выше, если только другие файлы токенов на том же клиенте не продолжают обновляться: тогда переподключить нужно только этот один файл. Ошибка 403 из-за квоты или прав, невключённый API или сетевой сбой — это другая проблема: скажите, какая именно, вместо просьбы переподключиться.
  • Никогда не отправляйте человека обратно в Google Cloud Console из-за расположения файла. Создание проекта, включение API и выпуск нового OAuth-клиента — долгий путь для неподготовленного пользователя, после которого остаётся второй набор учётных данных.

Личный и рабочий аккаунты бок о бок

Вы можете подключить личный аккаунт Google в одном рабочем пространстве и рабочий аккаунт (например, info@yourcompany.kz) в другом — они никогда не смешиваются. Каждое рабочее пространство Plank хранит свои scripts/google/credentials.json и token.json, поэтому ваш личный ящик и рабочий ящик остаются чётко разделёнными. Чтобы сменить аккаунт, который использует рабочее пространство, удалите его token.json и авторизуйтесь заново.

Как это вас защищает

  • Plank никогда не видит ваш пароль Google. Вы входите на собственной странице Google; обратно приходит только разрешение (токен).
  • Учётные данные остаются в вашем рабочем пространстве и исключены из git — они не передаются другим рабочим пространствам и никуда не коммитятся.
  • Вы можете отозвать доступ в любой момент в настройках безопасности вашего аккаунта Google или удалив файл токена.

См. также: Интеграции — общий взгляд на подключение сервисов, Скрипты рабочего пространства и боковая панель — как инструменты Google появляются после подключения, и Автоматизации — чтобы ассистент проверял почту по расписанию.