22ad20337a
У кабинета crm.bp-gr нет денежного баланса — есть «Баланс ГЦК» (остаток номеров)
в выпадашке шапки (table.balancetbl). supplier-balance.js логинится, раскрывает
выпадашку, читает «Баланс ГЦК» -> {numbers}. Провайдер: деньги = numbers ×
number_price_rub (20 ₽/шт, подтверждено владельцем). Live: 3096 -> 61 920 ₽.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
104 lines
4.4 KiB
JavaScript
104 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Headless Playwright чтение остатка НОМЕРОВ в кабинете поставщика crm.bp-gr.ru.
|
||
*
|
||
* У кабинета нет денежного баланса: в шапке выпадашка «Баланс» (js-dropdown,
|
||
* data-toggle="dropdown") с таблицей table.balancetbl. Нужная строка —
|
||
* «Баланс ГЦК» = количество доступных номеров (подтверждено владельцем 28.06).
|
||
* Деньги считаются на стороне PHP: номера × number_price_rub (20 ₽/шт).
|
||
*
|
||
* Логин-флоу — копия refresh-session.js (Yii2 LoginForm).
|
||
*
|
||
* Input (JSON через stdin): {login, password, url}
|
||
* Output (JSON через stdout): {numbers: <int>}
|
||
*
|
||
* Exit codes:
|
||
* 0 — success (число номеров найдено)
|
||
* 1 — auth failed (логин/пароль отклонены)
|
||
* 2 — строка «Баланс ГЦК» не найдена (разметка кабинета изменилась)
|
||
* 3 — timeout (60s)
|
||
* 4 — invalid input или другая ошибка
|
||
*/
|
||
const { chromium } = require('playwright');
|
||
|
||
const TIMEOUT_MS = 60_000;
|
||
|
||
async function readNumbers(args) {
|
||
let browser = null;
|
||
try {
|
||
browser = await chromium.launch({ headless: true });
|
||
const context = await browser.newContext();
|
||
const page = await context.newPage();
|
||
|
||
await page.goto(args.url, { waitUntil: 'load', timeout: TIMEOUT_MS });
|
||
|
||
const loginSelector = '#loginform-username';
|
||
await page.fill(loginSelector, args.login);
|
||
await page.fill('#loginform-password', args.password);
|
||
await page.click('button[type=submit]');
|
||
await page
|
||
.waitForFunction((sel) => !document.querySelector(sel), loginSelector, { timeout: TIMEOUT_MS })
|
||
.catch(() => {});
|
||
await page.waitForLoadState('networkidle', { timeout: TIMEOUT_MS }).catch(() => {});
|
||
|
||
if ((await page.locator(loginSelector).count()) > 0) {
|
||
process.stderr.write(JSON.stringify({ error: 'login rejected: still on login page after submit' }));
|
||
process.exit(1);
|
||
}
|
||
|
||
// Раскрыть выпадашку «Баланс» (js-dropdown в шапке), затем прочитать table.balancetbl.
|
||
await page.getByText('Баланс', { exact: true }).first().click().catch(() => {});
|
||
await page.waitForTimeout(1500);
|
||
|
||
// Найти в таблице баланса строку «Баланс ГЦК» и извлечь число.
|
||
const numbers = await page.evaluate(() => {
|
||
const rows = Array.from(document.querySelectorAll('table.balancetbl tr'));
|
||
for (const tr of rows) {
|
||
const cells = tr.querySelectorAll('td');
|
||
if (cells.length >= 2 && /Баланс\s*ГЦК/i.test(cells[0].textContent || '')) {
|
||
const raw = (cells[1].textContent || '').replace(/[^\d-]/g, '');
|
||
const n = parseInt(raw, 10);
|
||
return Number.isFinite(n) ? n : null;
|
||
}
|
||
}
|
||
return null;
|
||
});
|
||
|
||
if (numbers === null) {
|
||
process.stderr.write(JSON.stringify({ error: 'строка «Баланс ГЦК» не найдена (разметка кабинета изменилась)' }));
|
||
process.exit(2);
|
||
}
|
||
|
||
process.stdout.write(JSON.stringify({ numbers }));
|
||
process.exit(0);
|
||
} catch (err) {
|
||
process.stderr.write(JSON.stringify({ error: err.message }));
|
||
process.exit(err.message && err.message.includes('Timeout') ? 3 : 4);
|
||
} finally {
|
||
if (browser) {
|
||
await browser.close();
|
||
}
|
||
}
|
||
}
|
||
|
||
let input = '';
|
||
process.stdin.on('data', (chunk) => { input += chunk; });
|
||
process.stdin.on('end', () => {
|
||
let args;
|
||
try {
|
||
args = JSON.parse(input);
|
||
} catch (e) {
|
||
process.stderr.write(JSON.stringify({ error: 'invalid JSON on stdin' }));
|
||
process.exit(4);
|
||
}
|
||
if (!args.login || !args.password || !args.url) {
|
||
process.stderr.write(JSON.stringify({ error: 'missing required keys: login, password, url' }));
|
||
process.exit(4);
|
||
}
|
||
readNumbers(args).catch((err) => {
|
||
const message = err && err.message ? err.message : String(err);
|
||
process.stderr.write(JSON.stringify({ error: message }));
|
||
process.exit(4);
|
||
});
|
||
});
|