9cc3f5e950
Хвосты денег Д1-Д6 и робота Р-х1-Р-х6 из приёмочного листа v12. Д1 цена, по которой заморожены деньги, записывается на кампанию. Пока поле было пустым, списание читало глобальную цену — админ менял её, и клиент платил больше обещанного при запуске. Д2 суточное списание берёт кампанию под замком строки. Ключ идемпотентности зависит от числа показов, поэтому два одновременных прогона получали разные ключи и списали бы клиента дважды. Д3 пауза, не дошедшая до Директа, больше не считается паузой: отказ 409, заморозка остаётся. Раньше реклама крутилась дальше, портал показывал паузу, а деньги были уже свободны. У возобновления поведение намеренно прежнее — иначе понадобилось бы пятое место разморозки, а их ровно четыре. Там же убрана мина строгого сравнения рубильника. Д4 не трогали — это вопрос владельца. Д5 рубильник Директа держит и служебный канал робота: выдача задания и приём отчёта ходили в живой кабинет мимо него. Д6 проверка рубильника приведена к общему виду: YANDEX_DIRECT_ENABLED=0 давало строку, которую строгое сравнение читало как включено. Р-х1 настройки читаются из .env робота, а не каталога запуска. Р-х2 файл-замок robot.lock: проход и поддержание входа больше не дерутся за профиль браузера. Занят — уходим молча, задание остаётся в очереди. Брошенный замок перехватывается через полчаса. Р-х3 письмо-алярм честно говорит, залиты ли уже креативы в кабинет. Побочно вскрылось, что тексты писем не проверялись ни одним тестом — транспорт вынесен в src/smtp.js. Р-х4 тест-пустышка про рабочую папку заменён настоящим: запуск из чужого каталога без явной папки. Проверено вырезанием. Р-х6 пустое значение в окружении читается как значение по умолчанию, мусор даёт внятную ошибку вместо тихого NaN. Портал 293/293, робот 57/57. Денежных выходов снятия заморозки по-прежнему четыре. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
57 lines
3.0 KiB
JavaScript
57 lines
3.0 KiB
JavaScript
/**
|
|
* Письма человеку. Два повода: «робот встал» и «робот сделал».
|
|
*
|
|
* Транспорт передаётся снаружи — так письма проверяются без настоящей почты. Настоящий
|
|
* транспорт живёт в отдельном файле src/smtp.js: он тянет nodemailer, а зависимости стоят
|
|
* только на боевом сервере, и пока импорт был здесь, тексты писем нельзя было проверить
|
|
* ни одним тестом.
|
|
*/
|
|
export function createMailer(transport, { from, to }) {
|
|
return {
|
|
/**
|
|
* `uploaded` — были ли креативы к этому моменту УЖЕ залиты в кабинет.
|
|
*
|
|
* Раньше письмо было одно на все случаи и всегда утверждало «робот остановился и ничего
|
|
* в кабинете не менял». Тем же письмом сообщали, например, «окно загрузки не закрылось» —
|
|
* а там файлы уже приняты и робот уже отчитался «готово». Человек читал, что ничего
|
|
* не произошло, и в кабинет не шёл. А идти надо: там лежат креативы, и повторная заливка
|
|
* оставит дубли, которые вычищаются только руками.
|
|
*/
|
|
async alarm({ step, reason, campaignId, screenshotPath, uploaded = false }) {
|
|
const tail = uploaded
|
|
? [
|
|
'🔴 Креативы В КАБИНЕТ УЖЕ ЗАГРУЖЕНЫ — робот дошёл до конца работы.',
|
|
'Загляните в кабинет и в очередь заданий: повторная заливка оставит дубли,',
|
|
'вычистить их можно только руками.',
|
|
]
|
|
: [
|
|
'Робот остановился и ничего в кабинете не менял.',
|
|
'Кампания осталась черновиком, деньги не потрачены.',
|
|
];
|
|
|
|
await transport.sendMail({
|
|
from,
|
|
to,
|
|
subject: `[Робот креативов] АЛЯРМ на шаге «${step}»`,
|
|
text: [
|
|
`Кампания: ${campaignId}`,
|
|
`Шаг: ${step}`,
|
|
`Причина: ${reason}`,
|
|
'',
|
|
...tail,
|
|
].join('\n'),
|
|
attachments: screenshotPath ? [{ path: screenshotPath }] : [],
|
|
});
|
|
},
|
|
|
|
async report({ campaignId, count }) {
|
|
await transport.sendMail({
|
|
from,
|
|
to,
|
|
subject: '[Робот креативов] Готово',
|
|
text: `Креативы загружены.\nКампания: ${campaignId}\nФайлов: ${count}`,
|
|
});
|
|
},
|
|
};
|
|
}
|