Files
portal/app/config/client_tg.php
T
Дмитрий 3c72560139
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
fix(телеграм): ложное «вход слетел» больше не хоронит кампанию клиента
Приёмка Г1 02.08.2026 прошла: задание прошло насквозь, робот создал кампанию
2234762 в кабинете МТС, заголовок объявления и ссылка проверены ГЛАЗАМИ на шаге
«Объявление». Песочница, 445 номеров из 450 опознано, деньги не двигались.
Следы пробы убраны, копия — app/storage/app/proba-priemki-2026-08-02.json.

Приёмка вскрыла мину. Первое из двух заданий упало с «Вход слетел — нужен
повторный логин», хотя вход был ЖИВ: тем же кодом робота минутой позже проверка
ответила «да». Кабинет МТС завис на пустой странице, робот принял это за
разлогин. Цена — одна осечка сразу хоронила кампанию: задание в failed,
кампания в failed, второй попытки нет. На приёмке это 1 прогон из 2.

Робот (bots/mts-telegram-ads):
- ensureLoggedIn в src/session.js — три попытки с паузой 5с; сбой самой проверки
  считается попыткой, а не падением;
- SessionLostError несёт признак retryable;
- runner.js во всех трёх режимах (запуск, чтение вердикта, пересдача) зовёт
  ensureLoggedIn и протаскивает retryable в отчёт;
- bin/poll.js протаскивает retryable из своей обёртки.

Портал (app):
- RobotResult читает retryable (только при ok:false);
- TgRobotController::done на временный отказ возвращает задание в очередь
  (queued, taken_at=null) и кампанию НЕ трогает, пока attempts < max_attempts;
- client_tg.robot.max_attempts, по умолчанию 3, env TG_ROBOT_MAX_ATTEMPTS.

Считаются выдачи задания, а не отказы: тем же счётчиком attempts пользуется
возврат по сроку аренды, поэтому бесконечного круга не выйдет.

Тесты писались красными: портал 254/254 (720 проверок, +4 новых),
робот 134/134 (+4 новых). Pint чист, статанализ 0 ошибок.
В phpstan-baseline.neon дописаны 27 записей про $this в Pest-замыканиях —
тем же порядком, что у соседнего TgRobotDoneTest.

На боевой НЕ выкачено.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 02:00:46 +03:00

89 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
return [
/*
|--------------------------------------------------------------------------
| Песочница Telegram-рекламы
|--------------------------------------------------------------------------
| По умолчанию ВКЛ (true) — робот доводит кампанию только до черновика
| (mode:'draft'), деньги не списываются, «запустить/оплатить» не жмётся.
| Живой режим (mode:'live' + списание) открывается ОСОЗНАННО (Сессия 6)
| выставлением TG_SANDBOX=false после проверки в песочнице.
*/
'sandbox' => filter_var(env('TG_SANDBOX', true), FILTER_VALIDATE_BOOL),
/*
|--------------------------------------------------------------------------
| Наценка на клиентскую цену Telegram-рекламы
|--------------------------------------------------------------------------
| Клиент платит цену С наценкой: клиентская смета = базовая смета тарифа
| (себестоимость-оценка) × markup. 1.40 = +40%. Строка для bcmath (scale 2).
| Меняется без правки кода через env TG_MARKUP.
*/
'markup' => (string) env('TG_MARKUP', '1.40'),
/*
|--------------------------------------------------------------------------
| Порог авто-пачки
|--------------------------------------------------------------------------
| Авто-режим копит новых лидов и запускает кампанию, когда наберётся столько
| кандидатов (МТС показывает рекламу базе, а не по одному номеру). Минимальная
| осмысленная пачка у МТС Маркетолог — 367 (спека §5).
*/
'auto_batch_threshold' => (int) env('TG_AUTO_BATCH', 367),
/*
|--------------------------------------------------------------------------
| Окно «зависшей модерации»
|--------------------------------------------------------------------------
| Кампания в статусе `moderating` дольше стольких ЧАСОВ считается зависшей
| (робот не смог прочитать вердикт: сломались селекторы кабинета / id протух).
| Уборщик уводит её в `needs_review` для ручного разбора, БЕЗ возврата брони
| (могла реально показываться). МТС проверяет ~4ч; окно с большим запасом.
*/
'moderation_stuck_hours' => (int) env('TG_MODERATION_STUCK_HOURS', 48),
/*
|--------------------------------------------------------------------------
| Node-робот кабинета МТС (bots/mts-telegram-ads)
|--------------------------------------------------------------------------
| У МТС нет API — кампанию доводит браузер-робот. TelegramRobotRunner
| запускает `node <script> --task <file>` в каталоге <cwd>. Воркер этой
| очереди живёт на Windows-машине с живой сессией МТС (профиль браузера).
*/
'robot' => [
'node' => env('TG_ROBOT_NODE', 'node'),
'script' => env('TG_ROBOT_SCRIPT', base_path('bots/mts-telegram-ads/bin/run.js')),
'cwd' => env('TG_ROBOT_CWD', base_path('bots/mts-telegram-ads')),
'timeout' => (int) env('TG_ROBOT_TIMEOUT', 300),
/*
| Как портал отдаёт работу роботу.
| 'process' — старый путь: запускаем node на СВОЕЙ машине (Symfony Process).
| Работает, только если робот стоит там же, где очередь портала.
| 'poll' — робот на своей машине сам ходит за заданиями в /api/tg-robot/*.
| Единственный рабочий путь, когда робот и портал на разных машинах.
*/
'transport' => (string) env('TG_ROBOT_TRANSPORT', 'process'),
/*
| Сколько минут задание считается «в работе», прежде чем его можно выдать заново.
| Робот может умереть, не отчитавшись; без этого задание зависло бы навсегда.
| Прогон робота идёт ~5 минут, берём с запасом.
*/
'poll_lease_minutes' => (int) env('TG_ROBOT_LEASE_MINUTES', 20),
/*
| Сколько раз задание можно выдать роботу, прежде чем временный отказ станет
| окончательным. Кабинет МТС часто виснет на пустой странице, и робот принимает
| это за разлогин — на приёмке 02.08.2026 из двух прогонов так упал один, при
| заведомо живой сессии. Без повтора одна такая осечка сразу хоронила кампанию
| клиента. Считаются выдачи (attempts), а не отказы: срок аренды возвращает
| задание тем же счётчиком.
*/
'max_attempts' => (int) env('TG_ROBOT_MAX_ATTEMPTS', 3),
],
];