Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
247 lines
11 KiB
JavaScript
247 lines
11 KiB
JavaScript
// JavaScript функция для поиска дела через страницу поиска суда
|
||
// Используется в n8n workflow ноде "ищем дело через поиск"
|
||
// Работает только для региональных судов (*.sudrf.ru)
|
||
|
||
export default async function ({ page, context }) {
|
||
// Получаем данные из переменных n8n workflow
|
||
const originalUrl = '{{ $json.link }}' || '{{ $json.url }}';
|
||
const case_number = '{{ $json.case_number }}' || '';
|
||
const uid = '{{ $json.uid }}' || '';
|
||
|
||
if (!originalUrl) throw new Error('❌ Не передан url');
|
||
if (!case_number && !uid) throw new Error('❌ Не передан номер дела или УИД для поиска');
|
||
|
||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||
|
||
// Определяем тип суда по URL
|
||
const isRegionalCourt = /\.sudrf\.ru/.test(originalUrl) && !/mos-(gorsud|sud)\.ru/.test(originalUrl);
|
||
|
||
if (!isRegionalCourt) {
|
||
return {
|
||
url: originalUrl,
|
||
source: new URL(originalUrl).hostname,
|
||
status: 'error',
|
||
error_type: 'not_regional_court',
|
||
error_message: 'Поиск через страницу поиска поддерживается только для региональных судов',
|
||
found_url: null,
|
||
message: 'Не региональный суд'
|
||
};
|
||
}
|
||
|
||
// Установка заголовков и поведения браузера
|
||
await page.setViewport({ width: 1920, height: 1080 });
|
||
await page.setExtraHTTPHeaders({
|
||
"Referer": "https://sudrf.ru/",
|
||
"Origin": "https://sudrf.ru",
|
||
"Accept-Language": "ru,en;q=0.9",
|
||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||
"Upgrade-Insecure-Requests": "1",
|
||
});
|
||
await page.setUserAgent(
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
||
);
|
||
|
||
try {
|
||
// Извлекаем параметры из исходного URL
|
||
const urlObj = new URL(originalUrl);
|
||
const hostname = urlObj.hostname;
|
||
const protocol = urlObj.protocol;
|
||
|
||
// Извлекаем srv_num и delo_id из параметров URL
|
||
// Если исходный URL уже на страницу дела (name_op=case), извлекаем параметры оттуда
|
||
// Если исходный URL на страницу поиска (name_op=sf), используем его как есть
|
||
const srvNum = urlObj.searchParams.get('srv_num') || '1';
|
||
const deloId = urlObj.searchParams.get('delo_id') || '';
|
||
const nameOp = urlObj.searchParams.get('name_op') || '';
|
||
|
||
// Формируем URL страницы поиска
|
||
// Если исходный URL уже на страницу поиска - используем его
|
||
// Если на страницу дела - формируем URL поиска из тех же параметров
|
||
let searchUrl;
|
||
if (nameOp === 'sf') {
|
||
// Уже на странице поиска
|
||
searchUrl = originalUrl;
|
||
} else {
|
||
// Формируем URL страницы поиска из параметров исходного URL
|
||
searchUrl = `${protocol}//${hostname}/modules.php?name=sud_delo&srv_num=${srvNum}&name_op=sf${deloId ? `&delo_id=${deloId}` : ''}`;
|
||
}
|
||
|
||
// Переходим на страницу поиска (предполагаем, что капча уже проверена/решена отдельной нодой)
|
||
await page.goto(searchUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
||
await sleep(500);
|
||
|
||
// Быстрая проверка капчи (если она всё ещё есть - возвращаем ошибку)
|
||
const hasCaptcha = await page.evaluate(() => {
|
||
const captchaSelectors = [
|
||
'img[src*="captcha"]',
|
||
'img[src*="recaptcha"]',
|
||
'.captcha',
|
||
'#captcha',
|
||
'iframe[src*="recaptcha"]',
|
||
'div[class*="captcha"]',
|
||
'div[id*="captcha"]'
|
||
];
|
||
return captchaSelectors.some(sel => document.querySelector(sel));
|
||
});
|
||
|
||
if (hasCaptcha) {
|
||
// Капча всё ещё присутствует - возвращаем ошибку
|
||
return {
|
||
url: originalUrl,
|
||
source: hostname,
|
||
status: 'error',
|
||
error_type: 'captcha_required',
|
||
error_message: 'Требуется решение капчи',
|
||
found_url: null,
|
||
message: 'На странице поиска требуется решение капчи. Сначала используйте ноду "проверяем капчу".'
|
||
};
|
||
}
|
||
|
||
// Закрыть баннеры cookies, если есть
|
||
try {
|
||
await page.waitForSelector("#cookie-disclaimer .cd-close-button, .cookie-accept, .cookie__close", { timeout: 2000 });
|
||
const btns = await page.$$("#cookie-disclaimer .cd-close-button, .cookie-accept, .cookie__close");
|
||
if (btns[0]) await btns[0].click();
|
||
} catch (_) {}
|
||
|
||
await sleep(300);
|
||
|
||
// Заполняем форму поиска
|
||
// Ищем поле для номера дела или УИД
|
||
const searchFields = await page.evaluate(() => {
|
||
const fields = [];
|
||
// Ищем input поля с подходящими именами
|
||
document.querySelectorAll('input[type="text"], input[type="search"]').forEach(input => {
|
||
const name = input.name || '';
|
||
const id = input.id || '';
|
||
const placeholder = (input.placeholder || '').toLowerCase();
|
||
const label = (input.closest('tr')?.querySelector('td:first-child')?.textContent || '').toLowerCase();
|
||
|
||
if (name.includes('case') || name.includes('delo') || name.includes('number') ||
|
||
name.includes('uid') || id.includes('case') || id.includes('delo') ||
|
||
placeholder.includes('номер') || placeholder.includes('дел') ||
|
||
label.includes('номер') || label.includes('дел')) {
|
||
fields.push({ name: name || id, type: 'name', selector: name ? `input[name="${name}"]` : `input#${id}` });
|
||
}
|
||
});
|
||
return fields;
|
||
});
|
||
|
||
// Заполняем поле поиска (приоритет: номер дела, затем УИД)
|
||
const searchValue = case_number || uid;
|
||
if (!searchValue) {
|
||
return {
|
||
url: originalUrl,
|
||
source: hostname,
|
||
status: 'error',
|
||
error_type: 'no_search_value',
|
||
error_message: 'Нет данных для поиска (номер дела или УИД)',
|
||
found_url: null,
|
||
message: 'Не указан номер дела или УИД для поиска'
|
||
};
|
||
}
|
||
|
||
if (searchFields.length === 0) {
|
||
return {
|
||
url: originalUrl,
|
||
source: hostname,
|
||
status: 'error',
|
||
error_type: 'search_form_not_found',
|
||
error_message: 'Не найдена форма поиска на странице',
|
||
found_url: null,
|
||
message: 'Не удалось найти поля формы поиска'
|
||
};
|
||
}
|
||
|
||
// Заполняем первое найденное поле
|
||
const fieldSelector = searchFields[0].selector;
|
||
await page.waitForSelector(fieldSelector, { timeout: 5000 });
|
||
await page.click(fieldSelector, { clickCount: 3 }); // Выделяем весь текст
|
||
await page.type(fieldSelector, searchValue, { delay: 50 }); // Уменьшена задержка
|
||
await sleep(300);
|
||
|
||
// Ищем и нажимаем кнопку поиска
|
||
const searchButtonClicked = await page.evaluate(() => {
|
||
const buttons = Array.from(document.querySelectorAll('input[type="submit"], button, input[type="button"]'));
|
||
const searchBtn = buttons.find(btn => {
|
||
const text = (btn.value || btn.textContent || '').toLowerCase();
|
||
return text.includes('найти') || text.includes('поиск') || text.includes('search') ||
|
||
text.includes('искать') || btn.type === 'submit';
|
||
});
|
||
if (searchBtn) {
|
||
searchBtn.click();
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
if (!searchButtonClicked) {
|
||
return {
|
||
url: originalUrl,
|
||
source: hostname,
|
||
status: 'error',
|
||
error_type: 'search_button_not_found',
|
||
error_message: 'Не найдена кнопка поиска',
|
||
found_url: null,
|
||
message: 'Не удалось найти кнопку поиска на странице'
|
||
};
|
||
}
|
||
|
||
// Ждём результатов поиска (используем более быстрый режим)
|
||
await page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 15000 }).catch(() => {});
|
||
await sleep(1000);
|
||
|
||
// Ищем ссылку на дело в результатах
|
||
const caseLink = await page.evaluate((searchValue) => {
|
||
// Ищем ссылки, которые содержат номер дела или УИД
|
||
const links = Array.from(document.querySelectorAll('a[href*="case_id"], a[href*="delo"], a[href*="name_op=case"]'));
|
||
for (const link of links) {
|
||
const href = link.href || link.getAttribute('href') || '';
|
||
// Проверяем, что ссылка ведёт на страницу дела
|
||
if (href.includes('name_op=case') || href.includes('case_id')) {
|
||
// Если href относительный, делаем его абсолютным
|
||
if (href.startsWith('/') || href.startsWith('modules.php')) {
|
||
return window.location.origin + (href.startsWith('/') ? href : '/' + href);
|
||
}
|
||
return href;
|
||
}
|
||
}
|
||
return null;
|
||
}, searchValue);
|
||
|
||
if (caseLink) {
|
||
return {
|
||
url: originalUrl,
|
||
source: hostname,
|
||
status: 'success',
|
||
error_type: null,
|
||
error_message: null,
|
||
found_url: caseLink,
|
||
message: 'Дело найдено через страницу поиска'
|
||
};
|
||
} else {
|
||
return {
|
||
url: originalUrl,
|
||
source: hostname,
|
||
status: 'error',
|
||
error_type: 'case_not_found',
|
||
error_message: 'Дело не найдено в результатах поиска',
|
||
found_url: null,
|
||
message: 'Не удалось найти дело в результатах поиска'
|
||
};
|
||
}
|
||
|
||
} catch (error) {
|
||
return {
|
||
url: originalUrl,
|
||
source: new URL(originalUrl).hostname,
|
||
status: 'error',
|
||
error_type: 'search_failed',
|
||
error_message: `Ошибка при поиске дела: ${error.message}`,
|
||
found_url: null,
|
||
message: `Ошибка при поиске: ${error.message}`
|
||
};
|
||
}
|
||
}
|
||
|