- Description
- Key Features
- Why Go?
- Configuration
- Commands
- Message Syntax
- Media Support
- MCP Server
- Technologies
- Getting Started
- Development
- Migrating from the Node.js version
- Описание
- Основные функции
- Почему Go?
- Конфигурация
- Команды
- Синтаксис сообщений
- Поддержка медиа
- MCP-сервер
- Технологии
- Начало работы
- Разработка
- Миграция с Node.js-версии
Go service that does two things in one process, one container, one binary:
- Telegram → Todoist bot. Forward messages or write text to the bot — it creates tasks in Todoist in the right project automatically. Messages arriving within the timer window merge into a single task.
- Todoist MCP server. Exposes full CRUD over HTTP at
/todoistso other AI agents (e.g. a personal assistant) can read and write your Todoist through standard MCP tools.
Originally a Node.js bot; rewritten in Go (2026-04) with the MCP server merged in from the personal_memory project — so everything Todoist-related now lives in one place.
- Automatic task creation: Text and forwarded messages become Todoist tasks. Messages within the timer window are merged into one task (first line = title, rest = description).
- Priority via
!p1–!p4: Add!p2to your message — the task gets priority 2. Marker is stripped from the final title. - Labels via
#tag: Write#workor#urgent— the task gets those labels. Multiple tags supported. Does not match#inside URLs. - Media support: Photos, videos, documents, audio, voice, animations, stickers, locations, contacts, polls, venues — all converted to Markdown links embedded in the task.
- Album / media-group support: Telegram albums are aggregated and attached as multi-line task content.
- URL & hyperlink detection: Plain URLs,
www.prefixes, and Telegramtext_linkentities become clickable Markdown links in Todoist. - Project mapping by username: Each Telegram user routes to a Todoist project via
PROJECT_USERS_*env vars. Unknown senders fall back to Inbox with a notification. - Auto-whitelist: Users listed in
PROJECT_USERS_*are automatically whitelisted — no duplicate config. - Bot-admin role: One user is admin via
BOT_ADMIN. Only they can run/list. - Optional due date:
AUTO_ADD_DUE_DATE=true→ every new task gets today's date. - Embedded MCP server:
/todoistendpoint with 7 tools,X-API-Keyprotected. Call it frompersonal-assistant, Claude Desktop, or any MCP client. - Docker-ready: Single static binary (~11 MB), alpine image, GitHub Actions builds & pushes to GHCR.
This repo once held a Node.js bot; the MCP server came from personal_memory, which itself was Python before being rewritten. When it came time to unify them, Go was the obvious target:
- Stack consistency. Every other Go service in my Personal stack (
personal_memory,personal_assistant,book,calendar-mcp) already uses the same libraries (chi,mcp-go,slog). Staying on Go means one toolchain, one set of idioms, and code can be copied between projects almost verbatim — which is literally what happened withinternal/todoist. mcp-gowas already battle-tested. The Go port of the MCP SDK was running inpersonal_memoryfor months. Rewriting the Python MCP server into Go gavemcp-goanother real-world consumer without rewriting the wire protocol layer.- One static binary, no runtime.
CGO_ENABLED=0 go build→ an ~11 MB binary that runs onFROM alpinewith nothing butca-certificates. The old Node image dragged in 200 MB of npm deps; a Python equivalent would pull in a similar amount of wheels. - Concurrency is free. Telegram long-polling + HTTP server + debounce timers run as three goroutines with a single
sync.Mutexaround the message buffer. In Python this would meanasyncio+aiohttp+ careful locking; in Node it was callback soup. - Strong typing catches bot-API mismatches at compile time.
tgbotapi.Message.Photo[]vs.Videovs.Document— the compiler forces you to handle every field rather than hopingmsg.photo?.[0]?.file_iddoesn't panic at runtime.
Parsed in internal/config/config.go. All values come from environment variables (.env is auto-loaded via godotenv).
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_TOKEN |
yes | — | Bot token from @BotFather |
TODOIST_TOKEN |
yes | — | Todoist API token (Settings → Integrations → Developer) |
API_KEY |
recommended | — | Required header value (X-API-Key) for MCP /todoist calls. Empty disables auth — dev only. |
PORT |
— | 3000 |
HTTP port for /health and /todoist |
BOT_ADMIN |
— | — | Admin Telegram username (with or without @). Gates /list. |
TIMER |
— | 5 |
Seconds to buffer messages before creating a task |
AUTO_ADD_DUE_DATE |
— | false |
true → every task gets today's date as due |
PROJECT_USERS_<NAME> |
— | — | Map project <NAME> to users, e.g. PROJECT_USERS_Work="@alice,@bob" |
ALLOWED_USERNAMES |
— | — | Extra whitelisted usernames, comma-separated, @ optional |
MAX_RETRIES |
— | 3 |
Retries for transient Todoist API failures (429, 5xx) |
RETRY_DELAY |
— | 1000 |
Initial retry backoff in ms (doubles each attempt) |
Access control notes:
@usernameentries inPROJECT_USERS_*auto-add to the whitelist.- Full-name entries (without
@) are used for routing but not whitelisting. - Empty whitelist = allow all users (legacy behaviour).
| Command | Who | Description |
|---|---|---|
/start |
Everyone | Welcome message |
/help |
Everyone | List commands (admin sees /list) |
/status |
Everyone | Bot status + your role + current timer/auto-date settings |
/list [project] |
Admin only | Show up to 20 active tasks, optionally filtered by project name |
Buy groceries !p2 #personal #home
!p2→ priority 2 (1 = normal, 4 = urgent). Defaults to 1 if absent.#personal #home→ labels. Multiple supported.
Both markers are stripped from the final task title.
Examples of what the bot does with common inputs:
| You send | Task title in Todoist |
|---|---|
Call Mom !p3 #family |
@you: Call Mom (priority 3, label family) |
Photo with caption Kitchen remodel !p2 |
@you: Kitchen remodel [фото](https://…) |
Forwarded message from @alice |
@alice: <original text> |
Read https://example.com |
@you: Read [https://example.com](https://example.com) |
All 10+ Telegram attachment types are rendered as clickable Markdown:
| Type | Rendered as |
|---|---|
| Photo | [фото](https://t.me/file/…) |
| Video | [видео (file.mp4)](https://t.me/file/…) |
| Document | [документ (report.pdf)](https://…) |
| Audio | [аудио (Title - Artist)](https://…) |
| Voice message | [голосовое сообщение](https://…) |
| Animation / GIF | [анимация](https://…) |
| Sticker | [стикер (😀)](https://…) |
| Location | [локация](https://www.google.com/maps?q=…) |
| Contact | [контакт](tel:+123…) — Name: phone |
| Poll | Inline question + options |
| Venue | [место](https://…) — Name, address |
The /todoist endpoint uses mcp-go's Streamable HTTP transport and is protected by X-API-Key.
7 tools exposed:
| Tool | Purpose |
|---|---|
get_projects |
List all Todoist projects |
get_labels |
List all labels |
get_tasks |
List tasks (optional project_id, filter, limit) |
create_task |
Create a task (content required; optional description, project_id, due_string/due_date, priority, labels) |
update_task |
Update an existing task (task_id required) |
delete_task |
Delete a task |
complete_task |
Mark a task complete |
Example mcp.json entry for Claude Desktop or personal-assistant:
{
"mcpServers": {
"todoist": {
"url": "http://todoist-bot:3000/todoist",
"headers": { "X-API-Key": "your-api-key" }
}
}
}GET /health returns {"status":"ok"} and is unauthenticated — useful for Docker healthchecks and Traefik probes.
Go 1.24 · go-telegram-bot-api/v5 · mark3labs/mcp-go · go-chi/chi/v5 · joho/godotenv · Todoist REST API v1 · Docker · GitHub Actions → GHCR.
- Telegram account and bot token from @BotFather
- Todoist account and API token (Settings → Integrations → Developer)
- Docker installed
git clone https://github.com/dzarlax/todoist_bot.git
cd todoist_bot
cp docker-compose_example.yml docker-compose.ymlEdit docker-compose.yml and fill in your values:
environment:
TELEGRAM_TOKEN: your_botfather_token
TODOIST_TOKEN: your_todoist_api_token
API_KEY: pick-a-strong-key
BOT_ADMIN: "@yourusername"
PROJECT_USERS_Work: "@alice,@bob"
PROJECT_USERS_Family: "@wife"
TIMER: 5
AUTO_ADD_DUE_DATE: "true"docker compose up -d
docker compose logs -f todoist-botdocker compose downgo run ./cmd/todoist-bot # requires .env with TELEGRAM_TOKEN, TODOIST_TOKEN
go test ./... # unit tests
go vet ./...
go build ./...CI runs go vet + go test on every push/PR and builds/pushes ghcr.io/dzarlax/todoist-bot:latest + :${sha} on pushes to main (.github/workflows/docker.yml).
If you're currently running the old Node bot (dzarlax/todoist_bot on Docker Hub), the upgrade is drop-in. Todoist data is untouched — the bot is stateless, it only talks to Todoist's cloud API.
Changes you need to know:
- Image:
dzarlax/todoist_bot(Docker Hub) →ghcr.io/dzarlax/todoist-bot(GHCR). Note the dash in the new name. - Container name:
todoist→todoist-bot(by convention — you can still set whatever you want). - Healthcheck URL: unchanged —
http://localhost:3000/health. - Tokens:
TELEGRAM_TOKEN/TODOIST_TOKEN— unchanged, reuse them as-is. - Project/user config:
PROJECT_USERS_*,BOT_ADMIN,ALLOWED_USERNAMES,TIMER,AUTO_ADD_DUE_DATE— all unchanged. - New:
API_KEY— only needed if you plan to call the MCP/todoistendpoint from another service. Leave empty/unset for pure bot-mode. - Removed (no longer needed):
LOG_LEVEL,NODE_ENV,MAX_RETRIES/RETRY_DELAY(now hardcoded sensible defaults — still overridable).
Step-by-step:
-
Stash your old token values. If they're in
docker-compose.yml, pull them out now — moving them into.envis recommended for the new setup.grep -E 'TELEGRAM_TOKEN|TODOIST_TOKEN|BOT_ADMIN|PROJECT_USERS|ALLOWED_USERNAMES' docker-compose.yml -
Stop and remove the old container. Don't worry — no volumes to lose.
docker compose down
-
Pull the latest
docker-compose_example.ymlfrom this repo (or update your existingdocker-compose.ymlby hand):wget https://raw.githubusercontent.com/dzarlax/todoist_bot/main/docker-compose_example.yml -O docker-compose.yml
-
Create
.envalongsidedocker-compose.ymlwith your tokens:TELEGRAM_TOKEN=<existing value> TODOIST_TOKEN=<existing value> API_KEY=<new, pick a strong random string>
-
Edit
docker-compose.ymlto setBOT_ADMIN,PROJECT_USERS_*, etc. — values carry over 1:1. -
Start up:
docker compose pull docker compose up -d docker compose logs -f todoist-bot
You should see
INFO starting todoist-botfollowed byINFO telegram bot started user=<your_bot_username>within a second. If the logs showtelegram init failed error="Not Found", double-check the Telegram token was copied intact (including the:and the secret part). -
Optional — wire up the MCP endpoint. Point any MCP client at
http://<host>:3000/todoistwithX-API-Key: <your API_KEY>. If you were previously hitting a Todoist MCP hosted in another service, switch its URL and key here.
Rollback, if needed: docker compose down and put back the old image: dzarlax/todoist_bot line + restart. No data migration to undo.
Go-сервис, который делает две вещи в одном процессе, одном контейнере, одном бинарнике:
- Telegram → Todoist бот. Пересылаете сообщение или пишете текст — бот создаёт задачу в нужном проекте Todoist. Сообщения в пределах таймера склеиваются в одну задачу.
- Todoist MCP-сервер. Полный CRUD по HTTP на
/todoist, чтобы другие AI-агенты (например,personal-assistant) могли читать и писать ваш Todoist через стандартные MCP-инструменты.
Изначально был Node.js-бот; переписан на Go (2026-04) с интеграцией MCP-сервера из проекта personal_memory — теперь всё, что касается Todoist, в одном месте.
- Автоматическое создание задач: Текстовые и пересланные сообщения становятся задачами Todoist. Сообщения в пределах таймера объединяются в одну задачу (первая строка — заголовок, остальные — описание).
- Приоритет через
!p1–!p4: Добавьте!p2в сообщение — задача получит приоритет 2. Маркер удаляется из итогового заголовка. - Ярлыки через
#tag: Пишете#workили#срочно— задача получит эти метки. Несколько поддерживается. Не ловит#внутри URL. - Поддержка медиа: Фото, видео, документы, аудио, голосовые, анимации, стикеры, локации, контакты, опросы, места — всё конвертируется в Markdown-ссылки в задаче.
- Альбомы / медиа-группы: Альбомы Telegram агрегируются и вкладываются многострочным описанием.
- Распознавание URL и гиперссылок: Обычные URL,
www.префиксы и Telegramtext_linkстановятся кликабельными Markdown-ссылками в Todoist. - Маппинг проектов по пользователю: Каждый Telegram-пользователь роутится в Todoist-проект через
PROJECT_USERS_*. Неизвестные отправители — в Inbox с уведомлением. - Автоматический вайтлист: Пользователи из
PROJECT_USERS_*автоматически добавляются в whitelist — без дубля конфига. - Роль админа: Один пользователь — админ через
BOT_ADMIN. Только он может вызывать/list. - Опциональная дата выполнения:
AUTO_ADD_DUE_DATE=true→ каждая новая задача получает срок на сегодня. - Встроенный MCP-сервер: Эндпоинт
/todoistс 7 инструментами, защищёнX-API-Key. Подключается изpersonal-assistant, Claude Desktop или любого MCP-клиента. - Docker-ready: Один статический бинарник (~11 МБ), alpine-образ, GitHub Actions пушат в GHCR.
Раньше тут жил Node.js-бот; MCP-сервер пришёл из personal_memory, который сам был на Python до переписывания. Когда встала задача объединить всё это в одном месте, Go был очевидным выбором:
- Консистентность стека. Все остальные Go-сервисы в моём Personal-стеке (
personal_memory,personal_assistant,book,calendar-mcp) уже используют одни и те же библиотеки (chi,mcp-go,slog). Остаться на Go — значит иметь один тулчейн, одни идиомы и возможность буквально копировать код между проектами — что и случилось сinternal/todoist. mcp-goуже обкатан. Go-порт MCP SDK работал вpersonal_memoryнесколько месяцев. Переписывая Python-версию MCP-сервера на Go, я получил ещё одного боевого потребителяmcp-goбез переписывания уровня протокола.- Один статический бинарник, без runtime.
CGO_ENABLED=0 go build→ ~11 МБ бинарник наFROM alpineиca-certificates. Старый Node-образ тянул 200 МБ npm-зависимостей; Python-эквивалент притащил бы похожий объём wheels. - Конкурентность бесплатная. Telegram long-polling + HTTP-сервер + debounce-таймеры — три горутины с одним
sync.Mutexвокруг буфера сообщений. На Python —asyncio+aiohttp+ аккуратная блокировка; на Node — callback-суп. - Строгая типизация ловит несовпадения с bot API на компиляции.
tgbotapi.Message.Photo[]против.Videoпротив.Document— компилятор заставляет обработать каждое поле, а не надеяться, чтоmsg.photo?.[0]?.file_idне упадёт в рантайме.
Парсится в internal/config/config.go. Все значения — из переменных окружения (.env автозагружается через godotenv).
| Переменная | Обяз. | По умолчанию | Описание |
|---|---|---|---|
TELEGRAM_TOKEN |
да | — | Токен бота от @BotFather |
TODOIST_TOKEN |
да | — | API-токен Todoist (Настройки → Интеграции → Разработчик) |
API_KEY |
рекоменд. | — | Значение заголовка X-API-Key для MCP-вызовов /todoist. Пустой отключает auth — только для dev. |
PORT |
— | 3000 |
HTTP-порт для /health и /todoist |
BOT_ADMIN |
— | — | Админ Telegram username (с @ или без). Открывает /list. |
TIMER |
— | 5 |
Секунды буферизации сообщений перед созданием задачи |
AUTO_ADD_DUE_DATE |
— | false |
true → каждая задача получает срок на сегодня |
PROJECT_USERS_<NAME> |
— | — | Маппинг проекта <NAME> на пользователей, например PROJECT_USERS_Work="@alice,@bob" |
ALLOWED_USERNAMES |
— | — | Дополнительные разрешённые пользователи, через запятую, @ опционально |
MAX_RETRIES |
— | 3 |
Повторы при временных ошибках Todoist API (429, 5xx) |
RETRY_DELAY |
— | 1000 |
Начальный retry backoff в мс (удваивается с каждой попыткой) |
Про контроль доступа:
- Записи
@usernameвPROJECT_USERS_*автоматически добавляются в whitelist. - Записи с полным именем (без
@) используются для роутинга, но не для whitelist. - Пустой whitelist = разрешить всем (legacy поведение).
| Команда | Кому | Описание |
|---|---|---|
/start |
Всем | Приветственное сообщение |
/help |
Всем | Список команд (админ видит /list) |
/status |
Всем | Статус бота + ваша роль + текущий таймер и авто-дата |
/list [проект] |
Только админ | До 20 активных задач, опционально фильтр по проекту |
Купить продукты !p2 #личное #дом
!p2→ приоритет 2 (1 = обычный, 4 = срочный). По умолчанию 1 если нет маркера.#личное #дом→ ярлыки. Несколько поддерживается.
Оба маркера удаляются из итогового заголовка задачи.
Примеры того, что бот делает с типичными вводами:
| Вы шлёте | Заголовок задачи в Todoist |
|---|---|
Позвонить маме !p3 #семья |
@вы: Позвонить маме (приоритет 3, ярлык семья) |
Фото с подписью Ремонт кухни !p2 |
@вы: Ремонт кухни [фото](https://…) |
Пересланное от @alice |
@alice: <оригинальный текст> |
Почитать https://example.com |
@вы: Почитать [https://example.com](https://example.com) |
Все 10+ типов вложений Telegram рендерятся как кликабельный Markdown:
| Тип | Рендерится как |
|---|---|
| Фото | [фото](https://t.me/file/…) |
| Видео | [видео (file.mp4)](https://t.me/file/…) |
| Документ | [документ (report.pdf)](https://…) |
| Аудио | [аудио (Название - Исполнитель)](https://…) |
| Голосовое | [голосовое сообщение](https://…) |
| Анимация / GIF | [анимация](https://…) |
| Стикер | [стикер (😀)](https://…) |
| Локация | [локация](https://www.google.com/maps?q=…) |
| Контакт | [контакт](tel:+123…) — Имя: телефон |
| Опрос | Вопрос + варианты встроенно |
| Место | [место](https://…) — Название, адрес |
Эндпоинт /todoist использует Streamable HTTP транспорт из mcp-go и защищён X-API-Key.
Экспонируются 7 инструментов:
| Инструмент | Назначение |
|---|---|
get_projects |
Список всех проектов Todoist |
get_labels |
Список всех меток |
get_tasks |
Список задач (опционально project_id, filter, limit) |
create_task |
Создать задачу (content обязателен; опционально description, project_id, due_string/due_date, priority, labels) |
update_task |
Обновить существующую задачу (task_id обязателен) |
delete_task |
Удалить задачу |
complete_task |
Завершить задачу |
Пример записи в mcp.json для Claude Desktop или personal-assistant:
{
"mcpServers": {
"todoist": {
"url": "http://todoist-bot:3000/todoist",
"headers": { "X-API-Key": "ваш-api-ключ" }
}
}
}GET /health возвращает {"status":"ok"} и не требует аутентификации — удобно для Docker healthcheck'ов и Traefik-проб.
Go 1.24 · go-telegram-bot-api/v5 · mark3labs/mcp-go · go-chi/chi/v5 · joho/godotenv · Todoist REST API v1 · Docker · GitHub Actions → GHCR.
- Аккаунт Telegram и токен бота от @BotFather
- Аккаунт Todoist и API-токен (Настройки → Интеграции → Разработчик)
- Установленный Docker
git clone https://github.com/dzarlax/todoist_bot.git
cd todoist_bot
cp docker-compose_example.yml docker-compose.ymlОткройте docker-compose.yml и заполните переменные:
environment:
TELEGRAM_TOKEN: ваш_токен_от_botfather
TODOIST_TOKEN: ваш_todoist_api_токен
API_KEY: выберите-сильный-ключ
BOT_ADMIN: "@ваш_username"
PROJECT_USERS_Work: "@alice,@bob"
PROJECT_USERS_Family: "@wife"
TIMER: 5
AUTO_ADD_DUE_DATE: "true"docker compose up -d
docker compose logs -f todoist-botdocker compose downgo run ./cmd/todoist-bot # нужен .env с TELEGRAM_TOKEN, TODOIST_TOKEN
go test ./... # юнит-тесты
go vet ./...
go build ./...CI запускает go vet + go test на каждый push/PR и собирает/пушит ghcr.io/dzarlax/todoist-bot:latest + :${sha} при пушах в main (.github/workflows/docker.yml).
Если сейчас у вас запущен старый Node-бот (dzarlax/todoist_bot на Docker Hub), обновление — drop-in. Данные Todoist не трогаются — бот stateless, он просто ходит в облачный API Todoist.
Что изменилось:
- Образ:
dzarlax/todoist_bot(Docker Hub) →ghcr.io/dzarlax/todoist-bot(GHCR). Обратите внимание на дефис в новом имени. - Имя контейнера:
todoist→todoist-bot(по соглашению — можно ставить любое). - Healthcheck URL: не изменился —
http://localhost:3000/health. - Токены:
TELEGRAM_TOKEN/TODOIST_TOKEN— без изменений, переиспользуйте as-is. - Конфиг проектов/пользователей:
PROJECT_USERS_*,BOT_ADMIN,ALLOWED_USERNAMES,TIMER,AUTO_ADD_DUE_DATE— без изменений. - Новое:
API_KEY— нужен только если будете вызывать MCP/todoistиз другого сервиса. Для чисто бот-режима можно не задавать. - Убрано (больше не нужно):
LOG_LEVEL,NODE_ENV,MAX_RETRIES/RETRY_DELAY(теперь sensible defaults зашиты в код, но всё ещё переопределяемые).
Пошагово:
-
Запишите куда-нибудь значения старых токенов. Если они прямо в
docker-compose.yml— вытащите их сейчас, в новом setup'е их рекомендуется держать в.env.grep -E 'TELEGRAM_TOKEN|TODOIST_TOKEN|BOT_ADMIN|PROJECT_USERS|ALLOWED_USERNAMES' docker-compose.yml -
Остановите и удалите старый контейнер. Волноваться не о чем — volumes для потери нет.
docker compose down
-
Подтяните свежий
docker-compose_example.ymlиз этого репо (или обновите свой вручную):wget https://raw.githubusercontent.com/dzarlax/todoist_bot/main/docker-compose_example.yml -O docker-compose.yml
-
Создайте
.envрядом сdocker-compose.ymlс токенами:TELEGRAM_TOKEN=<старое значение> TODOIST_TOKEN=<старое значение> API_KEY=<новое, сгенерируйте сильную случайную строку>
-
Отредактируйте
docker-compose.yml: проставьтеBOT_ADMIN,PROJECT_USERS_*и т.д. — значения переносятся 1:1. -
Запускайте:
docker compose pull docker compose up -d docker compose logs -f todoist-bot
В логах в течение секунды должны появиться
INFO starting todoist-botиINFO telegram bot started user=<имя_вашего_бота>. Если видитеtelegram init failed error="Not Found"— проверьте, что Telegram-токен скопирован целиком (включая:и секретную часть). -
Опционально — подключите MCP-эндпоинт. Направьте любой MCP-клиент на
http://<host>:3000/todoistсX-API-Key: <ваш API_KEY>. Если раньше ходили в Todoist MCP, хостящийся в другом сервисе, — смените URL и ключ здесь.
Откатиться, если понадобится: docker compose down и вернуть старую строку image: dzarlax/todoist_bot + перезапуск. Никакой миграции данных откатывать не нужно.