docs телеграм: замысел и план кнопки «Остановить рекламу»
Кнопка остановки идущей рекламы + закрытие кампании по слову кабинета «Завершена». Прежний запрет на нажатие «Завершить» снят владельцем, разведка проведена живьём: кабинет отвечает «Останавливается, до 30 минут», закончив пишет «Завершена» и сам возвращает неоткрученный остаток целиком (+171,84 ₽ из 182,40 на кампании 2237821). Решения владельца: деньги клиенту возвращаем по слову «Завершена», а не в момент нажатия — до получаса реклама ещё крутится и тратит; кнопка только у запущенной рекламы, для остальных состояний кнопка кабинета не замерена. Разведка кода вскрыла три мины, все зафиксированы в замысле: 1. читалка собирает весь текст ряда вместе с подписями кнопок, а у идущей рекламы в ряду стоит кнопка «Завершить» — совпадение по корню закрывало бы работающую рекламу и возвращало деньги за то, что крутится прямо сейчас; 2. очередь заданий роботу считает одинаковыми любые два задания по одной кампании — просьба остановить при висящем чтении цифр молча терялась бы; 3. та же подстрока губительна и для опознавателя ряда, не только для разбора. План: 11 задач, каждая сперва красным сторожем. Кода не трогает — только документы. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,197 @@
|
||||
# Телеграм: кнопка «Остановить рекламу» и закрытие по слову кабинета
|
||||
|
||||
**Дата:** 08.08.2026
|
||||
**Повод:** владелец сказал «делай кнопку». Прежний запрет на нажатие «Завершить» в кабинете
|
||||
МТС снят ещё утром, разведка проведена живьём — знаем и слово кабинета, и что происходит с
|
||||
деньгами.
|
||||
|
||||
## Что уже известно из разведки (не гипотезы — замеры 08.08.2026)
|
||||
|
||||
Кнопку «Завершить» нажали на живой кампании портала №15 (в МТС `2237821`):
|
||||
|
||||
| Что | Ответ |
|
||||
|---|---|
|
||||
| Слово кабинета для законченной кампании | **«Завершена»** |
|
||||
| Слово, пока идёт остановка | **«Останавливается»** + «Это займёт до 30 минут» |
|
||||
| Реклама встала? | Да: показы застыли на 22, расход площадки 10,56 ₽ |
|
||||
| Баланс кабинета до / после | 4 812,51 ₽ → **4 984,35 ₽** |
|
||||
| Вернулось от площадки | **+171,84 ₽ — ровно `182,40 − 10,56`** |
|
||||
| Кнопки у завершённой строки | только «Посмотреть настройки» — запустить заново нельзя |
|
||||
|
||||
🔑 **Кабинет отдаёт неоткрученный остаток целиком и сам, без просьбы.** Значит наш возврат —
|
||||
про деньги КЛИЕНТА, а не про деньги площадки: их МТС уже вернул.
|
||||
|
||||
Первоисточник и хронология — план
|
||||
[2026-08-08-telegram-konec-reklamy-i-vozvrat.md](../plans/2026-08-08-telegram-konec-reklamy-i-vozvrat.md).
|
||||
|
||||
## Решения владельца
|
||||
|
||||
1. **Деньги возвращаем, когда кабинет скажет «Завершена»**, а не в момент нажатия. До
|
||||
получаса реклама ещё крутится и тратит; вернуть сразу — вернуть лишнее из своего кармана.
|
||||
2. **Кнопка только у запущенной рекламы.** Есть ли «Завершить» у той, что на проверке или
|
||||
ждёт старта, — НЕ замерено. Выдумывать не будем. До запуска у клиента и так есть «Отменить».
|
||||
|
||||
## Что делаем
|
||||
|
||||
### Ч1. Портал узнаёт конец рекламы от кабинета
|
||||
|
||||
Это фундамент: без него кнопка была бы враньём — портал сказал бы «остановлено», а деньги
|
||||
вернулись бы только через трое суток по правилу «встала».
|
||||
|
||||
**Робот учится двум словам.** `parseModerationStatus` в `bots/mts-telegram-ads/src/cabinet.js`
|
||||
знает шесть слов и ни одного из наших. Добавляем два новых канонических значения:
|
||||
|
||||
- `completed` — кабинет написал «Завершена»;
|
||||
- `stopping` — кабинет написал «Останавливается». Это ещё НЕ конец: показы идут, деньги не
|
||||
вернулись.
|
||||
|
||||
🔴 **Мина в подстроках — вскрыта при разведке кода, стоит денег.** Читалка собирает ВЕСЬ текст
|
||||
строки, включая подписи кнопок, а у запущенной кампании в строке живёт кнопка **«Завершить»**.
|
||||
Совпадай мы по корню `заверш` — каждая идущая реклама читалась бы как законченная, портал
|
||||
закрывал бы её и возвращал клиенту деньги за рекламу, которая крутится. Сравниваем только по
|
||||
полным словам `завершена` / `завершено`, и точно так же `останавливается`, а не `останов`.
|
||||
Красный сторож на это обязателен: строка с кнопкой «Завершить» и статусом «Активна» обязана
|
||||
читаться как `approved`.
|
||||
|
||||
Порядок проверок: новые слова идут СРАЗУ после «отклонен», до `одобрен|активн|запущен` —
|
||||
в строке-блобе рядом могут стоять оба, и побеждать должно последнее по времени состояние.
|
||||
|
||||
**Робот передаёт сырой текст.** Сейчас `readCampaignStats` отдаёт только разобранное слово, а
|
||||
исходный текст строки выбрасывает. Из-за этого запись в журнале «слово незнакомое» приходит
|
||||
БЕЗ самого слова, и следующее новое слово мы снова потеряем и снова полезем в кабинет глазами.
|
||||
Добавляем в ответ `rowText` — склеенный текст строки, обрезанный до 300 знаков.
|
||||
|
||||
**Портал закрывает кампанию по слову.** `TelegramStatsApplier` сперва, как и сейчас, кладёт
|
||||
свежие цифры, а затем: кабинет сказал `completed` → зовём `TelegramZavershenieService` закрыть
|
||||
кампанию и вернуть переплату с пометкой `zavershena-kabinetom`. Порядок важен — считать возврат
|
||||
надо по ТОЛЬКО ЧТО прочитанному расходу, а не по вчерашнему.
|
||||
|
||||
Прежние два правила (открутилась полностью / встала на трое суток) остаются подстраховкой на
|
||||
случай, если кабинет снова сменит слово.
|
||||
|
||||
Расход неизвестен, а кабинет говорит «Завершена» — закрывать нечем: уходим на ручной разбор
|
||||
существующим путём `nemaya`, а не закрываем молча с нулевым возвратом.
|
||||
|
||||
**Побочный выигрыш:** закрытие по слову работает для ЛЮБОЙ рекламы, а не только остановленной
|
||||
руками. Отработавшая сама закроется в течение часа, а не через трое суток.
|
||||
|
||||
### Ч2. Кнопка
|
||||
|
||||
**Что видит клиент.** У запущенной рекламы в карточке — красная кнопка «Остановить рекламу».
|
||||
Окно подтверждения говорит правду:
|
||||
|
||||
> Реклама остановится насовсем — запустить эту же обратно нельзя, только создать новую.
|
||||
> Остановка занимает до 30 минут, всё это время показы ещё идут.
|
||||
> Деньги за непоказанное вернутся на рекламный кошелёк, когда площадка закончит.
|
||||
|
||||
Подтвердил — кнопка сменяется надписью «Останавливается…», второй раз нажать нельзя.
|
||||
|
||||
**Что делает портал.** Новая ручка `POST /api/telegram/campaigns/{id}/stop`:
|
||||
|
||||
- пускает только `launched` с непустым `mts_campaign_id`, иначе 422;
|
||||
- второе нажатие (отметка уже стоит) — 422, задания не плодим;
|
||||
- ставит отметку `stop_requested_at` и кладёт роботу задание нового вида `stop`;
|
||||
- **денег не трогает вовсе.**
|
||||
|
||||
🔴 **Вторая мина, тоже из разведки кода.** `TelegramRobotQueue::enqueue` возвращает уже
|
||||
существующее незавершённое задание по кампании — ЛЮБОГО вида. Нажатие «Остановить» при висящем
|
||||
задании на чтение цифр молча вернуло бы чужое задание: портал сказал бы «останавливаем», а
|
||||
роботу никто ничего не передал бы. Чиним: одинаковыми считаем задания одного вида, а не любые
|
||||
два по одной кампании. Двум заданиям в очереди ничего не грозит — робот и так берёт строго по
|
||||
одному за раз.
|
||||
|
||||
**Что делает робот.** Новый вид работы `stop`:
|
||||
|
||||
1. открывает список кабинета и находит строку ИМЕННО этой кампании — тем же поиском строки,
|
||||
что уже работает при чтении цифр (подъём вверх, пока в поддереве ровно один номер кампании);
|
||||
2. ищет «Завершить» **внутри найденной строки**. 🔴 В разведке считали по всей странице и
|
||||
требовали ровно одну — это было верно только потому, что запущенная кампания была одна. У
|
||||
двух клиентов сразу так завершили бы чужую рекламу;
|
||||
3. жмёт; в окне подтверждения жмёт только кнопку из белого списка слов — рядом в кабинете
|
||||
живут кнопки удаления;
|
||||
4. **перечитывает строку и доказывает, что слово сменилось** на «Останавливается» или
|
||||
«Завершена». Кабинет обновляет строку не мгновенно — перечитываем несколько раз с паузой.
|
||||
Не сменилось — честный отказ, а не «наверное получилось».
|
||||
|
||||
Отчёт робота — той же формы, что у чтения цифр (`readCampaignStats` вызывается для проверки),
|
||||
плюс признак `stopped`. Разбирает его новый `TelegramOstanovkaApplier` — отдельная ветка в
|
||||
`TgRobotController` рядом с уже существующими: цифры и слово он отдаёт готовому
|
||||
`TelegramStatsApplier`, а сам занимается только отметкой. Значит портал применит и свежие
|
||||
цифры, и слово: успел кабинет написать «Завершена» сразу — кампания закроется тут же, написал
|
||||
«Останавливается» — закроется на ближайшем часовом заходе опросчика.
|
||||
|
||||
**Если не получилось.** Вход в кабинет протухает, это бывало.
|
||||
|
||||
Временный отказ (`retryable`) портал уже умеет возвращать в очередь сам, до всякого разбора
|
||||
отчёта: задание уходит на повтор, отметка `stop_requested_at` остаётся, клиент по-прежнему
|
||||
видит «Останавливается…». Робот попробует снова через минуту.
|
||||
|
||||
Отметку снимаем только на ОКОНЧАТЕЛЬНОМ отказе — когда попытки кончились
|
||||
(`client_tg.robot.max_attempts`) или отказ невосстановимый. Тогда портал не врёт: кнопка
|
||||
возвращается, в карточке появляется «Остановить не получилось, попробуйте ещё раз», и в журнал
|
||||
падает запись уровнем `warning` — на боевом ниже не доезжает никогда.
|
||||
|
||||
## Что меняется в хранилище
|
||||
|
||||
Одна колонка: `client_tg_campaigns.stop_requested_at` (timestamptz, null) — «когда клиент
|
||||
попросил остановить». Запись в журнале схемы — следующая свободная версия за v9.74 (ожидаемо
|
||||
v9.75; номер занимать в последний момент, в эту же ветку коммитит соседняя смена, и столкновение
|
||||
номеров журнала нельзя «разрешить» выбором стороны).
|
||||
|
||||
Нового статуса кампании НЕ заводим. Рассматривали — тянет за собой переходы, подписи на
|
||||
экране, все места, где перечислены статусы, и опросчика цифр пришлось бы учить брать ещё и
|
||||
его. Видимого выигрыша для клиента ноль, а мест соврать больше.
|
||||
|
||||
## Границы: чего тут НЕТ
|
||||
|
||||
- **Остановки рекламы, которая ещё на проверке МТС или ждёт старта.** Не замерено, есть ли
|
||||
там кнопка и что она делает с замороженными деньгами. Решение владельца — не выдумывать.
|
||||
- **Возврата денег в момент нажатия.** Решение владельца — ждать слова кабинета.
|
||||
- **Запуска остановленной обратно.** Кабинет этого не даёт: у завершённой строки остаётся
|
||||
только «Посмотреть настройки». Клиенту доступно «Повторить эту рекламу» — она создаёт новую.
|
||||
|
||||
## Сторожа
|
||||
|
||||
Каждый кусок — сперва красный сторож, потом код.
|
||||
|
||||
**Разборщик слов (робот):**
|
||||
|
||||
- строка с кнопкой «Завершить» и статусом «Активна» → `approved`, НЕ `completed` (мина №1);
|
||||
- «Завершена» → `completed`; «Останавливается» → `stopping`;
|
||||
- прежние шесть слов читаются как раньше.
|
||||
|
||||
**Очередь заданий (портал):**
|
||||
|
||||
- при висящем задании на чтение цифр просьба остановить создаёт ОТДЕЛЬНОЕ задание (мина №2);
|
||||
- повторная просьба того же вида нового задания не создаёт.
|
||||
|
||||
**Ручка остановки:**
|
||||
|
||||
- не-`launched` → 422; без номера кабинета → 422; вторая просьба → 422;
|
||||
- успешная просьба ставит отметку и кладёт задание; денег не двигает.
|
||||
|
||||
**Деньги:**
|
||||
|
||||
- слово «Завершена» закрывает кампанию и возвращает переплату один раз (ключ
|
||||
`tg:campaign:{id}:vozvrat`); повтор второй раз не возвращает;
|
||||
- слово «Останавливается» НЕ закрывает и денег НЕ трогает;
|
||||
- «Завершена» при неизвестном расходе → ручной разбор, а не тихое закрытие с нулём.
|
||||
|
||||
**Робот, нажатие:**
|
||||
|
||||
- две запущенные кампании в списке — жмём в СВОЕЙ строке;
|
||||
- слово после нажатия не сменилось → отказ, а не успех.
|
||||
|
||||
**Экран:**
|
||||
|
||||
- кнопка видна только у запущенной; после просьбы — «Останавливается…», нажать нельзя;
|
||||
- отказ возвращает кнопку и показывает человеческую причину.
|
||||
|
||||
## Красные линии
|
||||
|
||||
1. **Кабинет МТС — боевой, там деньги.** Нажатие необратимо. Приёмка на живой кампании — только
|
||||
с разрешения владельца и на той, которую не жалко.
|
||||
2. **Робот живёт в двух копиях** — `bots/mts-telegram-ads` в репозитории и рабочая
|
||||
`C:\liderra\mts-telegram-robot`. Разойдутся — **сводить, а не копировать поверх**.
|
||||
3. **Боевой прод liderra.ru — живые клиенты и деньги.** Выкат — отдельным шагом, только с
|
||||
разрешения владельца, после зелёных сторожей.
|
||||
Reference in New Issue
Block a user