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>
41 lines
2.1 KiB
JavaScript
41 lines
2.1 KiB
JavaScript
import { loadEnvFile } from '../src/env.js';
|
|
import { loadConfig } from '../src/config.js';
|
|
import { createPortal } from '../src/portal.js';
|
|
import { openBrowser } from '../src/browser.js';
|
|
import { isLoggedIn } from '../src/session.js';
|
|
import { uploadCreatives } from '../src/cabinet.js';
|
|
import { createMailer } from '../src/mailer.js';
|
|
import { smtpTransport } from '../src/smtp.js';
|
|
import { runOnce } from '../src/runner.js';
|
|
import { acquireLock, robotLockPath } from '../src/lock.js';
|
|
|
|
// Настройки читаем из .env РОБОТА, а не того каталога, откуда его запустило расписание.
|
|
loadEnvFile();
|
|
|
|
// Один проход: взять задание, если оно есть, и довести до конца. Запускается расписанием.
|
|
|
|
// 🔴 Замок берём ДО того, как спросить у портала работу. Профиль браузера один, Chromium
|
|
// держит его под своим замком, и второй процесс просто не стартует — а падало это уже
|
|
// ПОСЛЕ выдачи задания, и задание помечалось сбойным, хотя ничего не сломано. Занят
|
|
// замок — уходим молча и с кодом 0, задание остаётся в очереди до следующего раза.
|
|
const lock = acquireLock(robotLockPath());
|
|
if (!lock.ok) {
|
|
console.log(JSON.stringify({ skipped: 'робот уже работает' }));
|
|
process.exit(0);
|
|
}
|
|
|
|
try {
|
|
const config = loadConfig();
|
|
const portal = createPortal(config);
|
|
const browser = { open: openBrowser, isLoggedIn, uploadCreatives };
|
|
const mailer = createMailer(smtpTransport(config.smtp), { from: config.alarmFrom, to: config.alarmTo });
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
|
|
const res = await runOnce(config, portal, browser, mailer, { timestamp });
|
|
|
|
console.log(JSON.stringify(res, null, 2));
|
|
process.exit(res.idle || res.ok ? 0 : 1);
|
|
} finally {
|
|
lock.release();
|
|
}
|