Files
portal/bots/mts-telegram-ads
Дмитрий 32df332901 fix(телеграм-реклама): заголовок объявления для рекламы сайта — кампания больше не встаёт
Приёмка глазами вскрыла: кабинет МТС требует «Заголовок объявления» (до 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>
2026-08-01 11:42:46 +03:00
..

МТС-бот «Реклама в 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 подробная карта кабинета МТС — какие кнопки/поля где находятся