Приёмка глазами вскрыла: кабинет МТС требует «Заголовок объявления» (до 40 знаков), когда в объявлении ссылка на САЙТ, а не на телеграм-канал. Робот про это поле не знал, «Продолжить» молча не срабатывало, кампания вставала на шаге «Объявление» — в бою уже ПОСЛЕ списания денег. Проверено живьём: 2234454 (сайт — встала) против 2234462 (канал — дошла до подтверждения) и 2234490 (сайт с заголовком — дошла). Портал спрашивает заголовок заранее, на создании черновика: обязателен только для не-телеграмной ссылки (App\Support\TelegramLink), колонка ad_headline varchar(40), поле на экране появляется по той же развилке. Робот заполняет его в кабинете. Три ловушки, добытые живыми прогонами (описаны в коде): - поле дорисовывается в ОТВЕТ на ссылку, с задержкой — надо ждать, а не спрашивать; - под описание подходит несколько элементов — нужен .first(); - серая надпись внутри поля НЕ placeholder, а нарисованная подпись: поиск по атрибуту давал ноль совпадений при видимом на снимке поле. Опознаём по видимой надписи. Тесты: робот 130/130, ClientTg 250/250, экран 23/23. Полный прогон бэкенда — те же 13 падающих классов до и после правки (ни одного в телеграм-части). В baseline статанализа добавлен известный ложный класс Pest для нового файла тестов. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
МТС-бот «Реклама в Telegram по своей базе»
1. Что это
Робот сам заходит в кабинет МТС Маркетолог (marketolog.mts.ru) и создаёт
рекламную кампанию в Telegram по загруженной базе телефонов клиентов. Работает
на Windows-сервере рядом с мостом LiderraMtsBridge (тот же сервер, что для
СМС МТС). У робота два режима: «черновик» (доходит до кнопки запуска,
ничего не тратит) и «боевой» (нажимает кнопку и списывает деньги с баланса
кабинета).
2. ⚠️ Важные предупреждения (прочитать первым)
- Кабинет МТС — это реальные деньги. На балансе кабинета лежат настоящие рубли. Робот в режиме «боевой» их тратит. Пока идёт наладка — запускать только «черновик».
- Живой вход в кабинет = доступ к деньгам. Профиль браузера бота (папка,
указанная в
MTS_BROWSER_PROFILE_DIR) — это как ключ от кассы: хранить на сервере, не копировать, никуда не выкладывать, не отправлять по почте/в чат. - Телефоны клиентов — персональные данные (152-ФЗ). Робот держит их только во временном файле на время прогона и удаляет сразу после — файл не остаётся на диске и не попадает в git. В боевых кампаниях использовать только базу с согласием клиентов на рекламные звонки/сообщения, не старые лиды без согласия.
3. Разовая настройка (один раз)
Установка зависимостей и браузера:
cd bots/mts-telegram-ads
npm install
npx playwright install chromium
Скопировать .env.example в .env и заполнить:
| Переменная | Что это |
|---|---|
MTS_BROWSER_PROFILE_DIR |
папка на диске, где браузер бота хранит вход в кабинет (профиль) |
MTS_CABINET_URL |
адрес главной страницы кабинета МТС Маркетолог |
MTS_TELEGRAM_URL |
адрес раздела «Реклама в Telegram» в кабинете |
SMTP_HOST, SMTP_PORT |
адрес и порт почтового сервера для отправки писем (Unisender Go) |
SMTP_USER, SMTP_PASS |
логин и пароль для отправки писем (те же креды, что у портала) |
ALARM_FROM |
с какого адреса приходят письма-алярмы/отчёты |
ALARM_TO |
на какой адрес слать письма-алярмы/отчёты |
BUDGET_CAP_RUB |
потолок бюджета в рублях: боевой запуск дороже этой суммы робот запрещает сам |
HUMAN_DELAY_MS |
пауза между действиями робота в кабинете (мс) — «по-человечески», не чтобы сайт заподозрил робота |
Разовый вход в кабинет. Один раз нужно открыть браузер бота в видимом режиме и вручную залогиниться (телефон/пароль + код по СМС) — после этого вход запомнится в профиле браузера, и робот дальше сможет заходить сам. Команда:
node bin/login.js
(или npm run login). Откроется окно браузера на странице кабинета — войти
руками, как обычно (телефон/пароль + код по СМС). После того как в окне
появится кабинет, вход сохранён в профиле браузера — окно можно закрыть. Это
действие делает владелец (у него доступ к телефону/паролю кабинета).
4. Файл с номерами
- Формат: по одному номеру в строке, вид
79000000000(11 цифр). Робот сам приводит к этому виду номера вида «+7…», «8…», убирает лишние символы и повторы. - Требование кабинета: минимум 367 номеров «не МТС». Реклама в Telegram по своей базе показывается только абонентам НЕ-МТС (по МТС-номерам из той же базы можно отправить СМС — в боте это выключено).
- Куда класть файл: в папку
screenshots/(она в.gitignore— номера не попадут в git). В примере задания путьscreenshots/test-phones.txt. - Где взять тестовую базу: XLSX-экспорт лидов из старого кабинета поставщика crm.bp-gr.ru — эту выгрузку делает владелец.
5. Формат задания (task.json)
| Поле | Обязательное | Что это |
|---|---|---|
mode |
да | draft (черновик, безопасно) или live (боевой, тратит деньги) |
phonesFile |
да | путь к файлу с номерами |
adText |
да | текст объявления (до 160 символов) |
adTitle |
да | заголовок объявления (до 40 символов) |
buttonUrl |
да | ссылка — что рекламируем (канал/бот/сайт) |
budgetRub |
да | бюджет кампании в рублях |
cpmRub |
да | цена за 1000 показов, рубли |
ordCompanyName |
да | название компании/сервиса — для маркировки рекламы (ОРД) |
ordCategory |
нет | категория товара/услуги для ОРД, по умолчанию «Размещение рекламы» |
mediaFile |
нет | путь к картинке/видео объявления |
clientTag |
нет | своя метка для учёта (не используется кабинетом) |
Пример (файл task.example.json):
{
"mode": "draft",
"phonesFile": "screenshots/test-phones.txt",
"adText": "Подключите наш телеграм-бот и получайте заявки быстрее",
"adTitle": "Лидерра — заявки на автопилоте",
"buttonUrl": "https://liderra.ru",
"budgetRub": 1000,
"cpmRub": 400,
"ordCategory": "Размещение рекламы",
"ordCompanyName": "Лидерра"
}
6. Запуск
Черновик (безопасно, ничего не тратит):
node bin/run.js --task task.example.json
Робот дойдёт до кнопки запуска, снимет скриншот шага и не нажмёт её. На
почту (ALARM_TO) придёт отчёт.
Боевой (тратит деньги): то же самое, но в задании "mode": "live". Запускать
только с разрешения владельца и в пределах потолка BUDGET_CAP_RUB — робот
сам сверяет бюджет задания с потолком и отказывается запускать, если бюджет
больше.
Результат прогона печатается в консоль в виде JSON, например
{"ok": true, "matched": 996, "launched": false} — matched это сколько
номеров «не МТС» нашлось, launched — нажал ли робот кнопку запуска.
6.2. Работа от портала — робот сам спрашивает задания (bin/poll.js)
Обычный запуск выше требует, чтобы робот стоял на той же машине, где очередь портала:
портал сам запускает bin/run.js. У нас так не выходит — МТС отбивает адреса
дата-центров, и робот живёт отдельно. Поэтому есть второй способ: портал кладёт задание
в очередь, а робот сам ходит за ним.
Что нужно в .env робота:
PORTAL_BASE_URL=https://liderra.ru
TG_ROBOT_TOKEN=<тот же секрет, что в .env портала>
Один проход:
node bin/poll.js
- работы нет → напечатает «Работы нет.» и выйдет с кодом 0;
- работа есть → заберёт задание, отдельным запросом скачает номера, отработает обычным
runTaskи отчитается на портал.
Своей петли внутри нет — проход запускается таймером (раз в минуту). Так проще убить и перезапустить, а зависший проход не мешает следующему: портал сам вернёт задание в очередь, если по нему не отчитались за срок аренды (по умолчанию 20 минут).
🔴 Номера телефонов робот не получает в задании — только отдельным запросом и только пока задание в работе. Кладёт их во временный файл и убирает при любом исходе.
Портал переключается на этот способ переменной TG_ROBOT_TRANSPORT=poll в своём .env.
Пока она process (умолчание) — работает старый способ, и poll.js заданий не увидит.
6.1. Пересдача отклонённой кампании (mode: "resubmit")
Если модерация МТС отклонила кампанию, робот может её починить: войти в редактор через «Исправить», внести исправления и снова отправить на модерацию без оплаты (0 ₽ — деньги не списываются).
Что исправляем — на выбор (в ответ на причину отказа): приложить документ модератору (лицензию/справку) и/или поправить текст/ссылку/категорию ОРД. Нужна хотя бы одна правка — иначе тот же контент снова отклонят. Номера/бюджет не нужны — они у кампании уже есть.
| Поле | Обязательное | Что это |
|---|---|---|
mode |
да | resubmit |
campaignId |
да | номер кампании, которую чиним (из кабинета) |
submitMode |
да | draft (дойти до конца, не отправлять) или live (отправить без оплаты) |
moderatorFile |
нет* | путь к документу для модератора (pdf/картинка) |
adText |
нет* | новый текст объявления |
buttonUrl |
нет* | новая ссылка (канал/бот/сайт) |
ordCategory |
нет* | новая категория ОРД |
* хотя бы одно из полей-правок (moderatorFile/adText/buttonUrl/ordCategory) обязательно.
{
"mode": "resubmit",
"campaignId": "2231134",
"submitMode": "live",
"moderatorFile": "screenshots/licenziya.pdf",
"adText": "Исправленный текст объявления"
}
submitMode: "live" реально отправляет кампанию на повторную модерацию (бесплатно) —
запускать только с разрешения владельца. Результат: {"ok": true, "campaignId": "2231134", "resubmitted": true}.
7. Присмотр за входом (keep-alive)
Команда node bin/keepalive.js тихо проверяет, жив ли вход в кабинет. Если
вход слетел — робот сам отправляет письмо-алярм на ALARM_TO.
Чтобы проверка шла сама каждые 15 минут, добавить задачу в Планировщик заданий Windows (пример команды, путь заменить на реальный на сервере):
schtasks /create /tn "LiderraMtsTgKeepAlive" /tr "node C:\путь\bots\mts-telegram-ads\bin\keepalive.js" /sc minute /mo 15 /ru СИСТЕМА
8. Что делать при алярме на почте
Письмо приходит с темой вида «[МТС-бот] АЛЯРМ на шаге «...»» и приложенным скриншотом. Частые причины:
- «Вход слетел — нужен повторный логин» — повторить разовый вход из раздела 3.
- «недостаточно номеров не-МТС» (меньше 367) — нужна база побольше.
- «Шаг «Стоимость» ещё не размечен» — это ожидаемо на сегодня, см. раздел 9.
9. Текущее состояние / что ещё не готово (честно)
Реализованы и проверены (юнит-тесты ядра — все зелёные, см. npm test) шаги:
- вход в визард кампании;
- выбор «Своя база клиентов»;
- загрузка базы и подсчёт «не МТС»;
- проверка порога 367 «не МТС» (ниже — честный алярм «недостаточно номеров не-МТС», кампанию не создаём) и переход со шага «Аудитория» на «Объявление» (кнопка «Продолжить», с само-проверкой, что попали на нужный шаг, а не заполняем объявление вслепую на чужой странице);
- заполнение объявления (текст, заголовок, ссылка, блок ОРД);
- финал: черновик — стоп перед кнопкой, боевой — нажатие кнопки.
Не готово:
- Шаг «Стоимость» (бюджет/CPM) в кабинете пока не размечен — точные места на экране, куда робот должен кликать, ещё не сняты вживую. До этого робот на этом шаге штатно останавливается и шлёт алярм — это ожидаемо, не поломка.
- Часть полей объявления (точное место поля «Заголовок», «Название компании» для ОРД, поле для картинки/видео) помечены в коде как требующие подтверждения — они снимаются на первом реальном черновике.
- Приёмка «боевого» режима (один контрольный запуск на минимальном бюджете) — только с явного разрешения владельца.
10. Устройство (кратко)
| Файл | Назначение |
|---|---|
src/config.js |
читает .env, проверяет, что все нужные настройки заданы |
src/task.js |
читает и проверяет задание (task.json) |
src/phones.js |
приводит номера к формату 79..., убирает мусор и повторы |
src/budget.js |
проверяет, что бюджет боевого запуска не выше потолка |
src/mailer.js |
отправка писем-алярмов и отчётов на почту |
src/browser.js |
открывает браузер с сохранённым профилем (живым входом) |
src/session.js |
проверяет, жив ли вход в кабинет |
src/cabinet.js |
шаги визарда кабинета: вход, загрузка базы, объявление, финал |
src/runner.js |
весь прогон целиком: собирает шаги по порядку, ловит ошибки, шлёт алярм/отчёт, чистит временный файл с номерами |
bin/run.js |
команда запуска одного задания |
bin/keepalive.js |
команда проверки живого входа (для Планировщика Windows) |
docs/cabinet-flow.md |
подробная карта кабинета МТС — какие кнопки/поля где находятся |