savereelsbot
Telegram-бот: по ссылке скачивает видео/фото из Instagram/YouTube/TikTok. Два режима — личка (юзер шлёт ссылку, бот отвечает видео) и приватный канал (бот-админ, автопостинг + удаление исходного сообщения).
Стек: Node.js + TypeScript, yt-dlp как child process, локальный telegram-bot-api (лимит аплоада 2GB вместо 50MB). Репо: Kobalt695/savereelsbot. Полный план — /home/claude/.claude/plans/cuddly-strolling-starlight.md.
Instagram-карусели (2026-07-07)
Добавлена поддержка carousel-постов (2-20 фото/видео в одном посте) — раньше падали с ошибкой No video formats found!.
- Причина:
--no-playlistне помогает — Instagram-экстрактор yt-dlp всё равно разворачивает карусель вentries, и yt-dlp пытается выбрать видео-формат для каждого элемента. У фото-элементовformatsпуст → hard error на весь--dump-json. - Фикс: флаг
--ignore-no-formats-error— yt-dlp продолжает и отдаёт JSON по всем элементам. Различаем фото/видео поentry.formats.length. У фото берёмentry.thumbnails.at(-1).url(полноразмерный CDN-URL, без даунскейла — подтверждено на реальном посте). У видео — доскачиваем через--playlist-items <playlist_index>на том же URL с обычным format-selector’ом (playlist_index1-based, строго по порядку entries — подтверждено). DownloadResultтеперь discriminated union (kind: 'video' | 'carousel') вtypes.ts. Новые методыsendPhoto/sendMediaGroupвtelegram.ts(multipart,attach://fileN, caption только на первом элементе — так группируется в Telegram). Лимит Telegram media group — 10 элементов, если фотка/видео одно — шлём как обычныйsendPhoto/sendVideo.- Проверено живьём: реальный пост
instagram.com/p/DadKfF3gSuL/(10 фото) — альбом ушёл, все 10 файлов. Regression: обычный reel по-прежнему качается как раньше (kind: 'video'). - TikTok/YouTube путь не тронут — carousel-ветка только для
platform === 'instagram'.
Статус (2026-07-07)
- ✅ Прототип yt-dlp-обёртки, локальный
telegram-bot-api, MVP личка, Instagram-карусели — работают (YouTube/Instagram качаются напрямую с текущего сервера без проблем) - 🔴 TikTok заблокирован с сервера (82.39.215.94, Hostkey DE, AS57043) — 302 на
/hk/notfoundдаже для@tiktok. Причина — датацентр-ASN в бан-листе TikTok, не гео - ⏳ Нужен residential-прокси только для TikTok-трафика (см. ниже). Отложено пользователем на потом — сейчас не покупаем, просто зафиксировали ресёрч
Проверено и отклонено
- nl-aeza (138.124.119.57) — другой VPS/ASN, тоже не помогло: curl проходил (200, без редиректа), но yt-dlp получал
"Your IP address is blocked from accessing this post"— это уже проверка репутации IP на уровне API TikTok, а не edge-редирект по ASN. Т.е. TikTok банит датацентр-диапазоны глубже, чем просто CDN-edge — любой VPS-провайдер (Hetzner/OVH/Aeza и т.п.) будет давать тот же результат. Доступ к nl-aeza (ключ) отозван и удалён после теста — не пригодился - 193.42.124.166 (прод-vpnbot) — сознательно не трогали, это прод-сервер реального бизнеса, не тестовая нода
Residential proxy — ресёрч провайдеров
Нужен именно residential (IP реального провайдера связи, выданный обычному человеку), не datacenter — TikTok категорически банит датацентр-ASN, но не может банить residential-диапазоны (иначе забанит обычных пользователей).
Проблема с оплатой из РФ
IPRoyal и Webshare ориентированы на западные карты — с российской картой, скорее всего, затык. Найдены RU-дружелюбные альтернативы:
| Провайдер | Оплата из РФ | Цена | Особенности |
|---|---|---|---|
| Froxy | Российские карты, СБП, крипта (детали оплаты) | от 1.99/3 дня (100MB) | Эстонская компания, 10M+ устройств, 200+ стран, 70k+ провайдеров связи |
| Proxy-Seller | Крипта (USDT/BTC), + рекомендуют платить именно RU-картой если есть выбор — возврат без комиссии сети | резидентные от $3.5/GB, полный прайс | 20M+ IP, 220+ стран, rotating (по времени/запросу/sticky), таргетинг по стране/городу/провайдеру |
| Западные карты, вероятны проблемы с RU | pay-as-you-go, $1.75-7/GB, прайс | Трафик не сгорает — но оплата под вопросом | |
| Западные карты, вероятны проблемы с RU | residential от $3.50/GB (промо), прайс | Free tier есть, но это datacenter-IP — для TikTok не годится |
Вывод: смотреть в сторону Froxy или Proxy-Seller из-за оплаты. Крипта (USDT) как universal fallback работает почти везде, включая IPRoyal/Webshare, если всё же захочется остаться на них.
Как это встраивается технически
Residential-прокси не даёт статичный IP на руки (кроме отдельного дорогого продукта “ISP/static residential” — не нужен нам). Работает через один постоянный gateway-адрес + логин:пароль, IP меняется прозрачно на стороне провайдера (rotating по времени/запросу, либо sticky-сессия). Для нас это просто:
TIKTOK_PROXY=http://user:pass@<gateway-host>:<port>
Один флаг --proxy в yt-dlp, применяется только к tiktok.com (YouTube/Instagram продолжают качаться напрямую). Rotating-режим (не sticky) — то что нужно, дешевле и даже лучше для нас (нет смысла держать сессию/один IP, каждый запрос независим).
Готча с окружением (уже решена, но важно помнить)
curl_cffi (нужен для TLS-impersonation, обход анти-бота TikTok независимо от прокси) — установлен в user site-packages /usr/bin/python3 (pip.pyz install --user --break-system-packages curl_cffi, т.к. apt/ensurepip/venv недоступны без sudo). Важно: ./bin/yt-dlp через shebang резолвит python3 из PATH, а там первым стоит venv [[projects/agent-second-brain]] (без curl_cffi) — нужно явно вызывать /usr/bin/python3 bin/yt-dlp ..., иначе impersonation молча не работает. При доработке downloader.ts (task #4) — учесть в spawn.
Следующий шаг
Пользователь отложил покупку прокси на потом. Когда вернёмся: выбрать Froxy или Proxy-seller, оплатить, получить gateway host:port:user:pass, воткнуть в downloader.ts (только для detectPlatform === tiktok).
Нейтральная подпись для карусели (commit 48a06b8)
Для carousel-постов заголовок вида Video by <username> заменяется на Пост от <username> (buildCaption() в [[index]].ts, regex /^Video by /i) — чтобы подпись при автопостинге в канал не выглядела мусорной/машинной.
systemd вместо ручного запуска (2026-07-07)
Бот переведён с ручного запуска на systemd user-unit:
~/.config/systemd/user/savereelsbot.service:ExecStart=node dist/[[index]].js(не ts-node — собранный JS),Restart=always,RestartSec=3, лог в/home/claude/savereelsbot/bot.log- Обновление:
npm run build && systemctl --user restart savereelsbot - Попутно починен git credential helper (
gh auth setup-git) — до этого push из-подclaudeспотыкался на аутентификации
Почему это делалось реактивно, а не сразу: пользователь ожидает, что crash recovery и переживание ребута — часть первой реализации любого долгоживущего сервиса, а не то, что предлагается только после того, как процесс уже падал.
Форвард постов канала “Идеи” в vault-inbox (2026-07-07)
Цель: посты из приватного канала “Идеи” (savereelsbot туда же и постит) не теряются, а попадают в vault заготовками для разбора.
Отброшенный вариант: форвард через Telegram в топик ccbot (message_thread_id). Не работает архитектурно — getChat на приватный чат ccbot с пользователем (30777924) вернул "type": "private", а не supergroup. Значит “топики” ccbot — его собственная эмуляция поверх одного flat-чата (вероятно по reply-цепочкам), а не нативные Telegram forum-topics. Нативный message_thread_id работает только в форум-супергруппах, и один бот физически не может писать в приватный чат другого бота (у savereelsbot и у ccbot — разные, изолированные приватные чаты с одним и тем же пользователем). Заводить отдельную группу с forum-режимом ради этого не стали — пользователь выбрал вариант проще.
Выбранный вариант — filesystem inbox: savereelsbot (юзер claude, тот же, что у Claude Code) сам пишет пост прямо в vault/thoughts/ideas/inbox/<timestamp>-<platform>/:
[[thoughts/ideas/inbox/1784195450310-instagram/post]].md— frontmatter (source,platform,created,status: new) + секции## Ссылка/## Подписьthumbnail.jpg(для video-постов, если скачался) илиphoto1.jpg,photo2.jpg, … (для carousel — все фото-элементы)
Разбор — автоматический, без напоминаний от пользователя: пункт 4 добавлен в обязательный чек-лист старта сессии в /home/claude/CLAUDE.md. Любая Claude Code сессия под /home/claude при первом сообщении проверяет inbox/, разбирает найденные папки в vault/thoughts/ideas/YYYY-MM-DD-slug.md (frontmatter: type/description/tags/status/created; секции Источник/Разбор/План) + строку в vault/MOC/[[MOC/MOC-ideas]].md, затем удаляет обработанную папку из inbox.
Код: saveToVaultInbox() в [[index]].ts, вызывается для isChannelPost после отправки в канал, до удаления скачанных файлов. VAULT_INBOX_DIR — env var с дефолтом на путь выше. Попутно добавлен захват thumbnail для одиночных видео (downloadThumbnail() в downloader.ts, VideoResult.thumbnailPath) — видео целиком в vault-inbox не положить (Claude Code не “смотрит” видео), только превью+подпись+ссылка. Коммиты: df5508b (первая версия через Telegram-топик, заменена), 532c609 (текущая filesystem-версия).
Канал сейчас пустой/debug — бэкафилл старых постов не делался.
Разбор inbox — АВТОМАТИЗИРОВАН 05.08 (был ненадёжен: держался на инструкции «разбери при старте сессии» в /home/claude/CLAUDE.md, по факту пробуксовывал — 05.08 лежали 4 папки, две с 02.08 без разбора 3 дня).
- Как сделано: разбор встроен в ежедневный
dbrain-process(21:00) тем же механизмом, что daily-обработка (persistent Claude-сессия d-brain, НЕclaude -p— биллинг на подписку).processor.py→ методprocess_inbox(): находит папки вvault/thoughts/ideas/inbox/, пустой inbox = быстрый no-op без обращения к сессии; иначе шлёт промпт разбора (оформитьYYYY-MM-DD-slug.md+ строка в MOC-ideas + rm папки).pipeline.py→ командаinboxроутит наprocess_inbox().scripts/process.sh→ шагpython -m d_brain.pipeline inboxпосле daily, до graph rebuild (новые заметки попадают в тот же граф + git-коммит).
- Проверено live 05.08: тестовая папка → d-brain создал заметку, добавил в MOC (со связью на реальные проекты Сергея), удалил папку. End-to-end работает. Инструкция в CLAUDE.md остаётся как подстраховка для интерактивных сессий.