Skip to content

Commit 86bb34d

Browse files
authored
Merge pull request #20 from chaichuk/odesa-parser-fix
2 parents 2f4c7ce + 4d2f4ba commit 86bb34d

File tree

1 file changed

+41
-27
lines changed

1 file changed

+41
-27
lines changed

src/monitor.js

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ function getKyivDate(offsetDays = 0) {
6666
// Функція паузи
6767
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
6868

69-
// 1. ДТЕК (Playwright) - МАКСИМАЛЬНО НАДІЙНА ВЕРСІЯ
69+
// 1. ДТЕК (Playwright) - ВИПРАВЛЕНА ВЕРСІЯ
7070
async function getDtekRegionInfo(browser, config) {
7171
if (!config.city || !config.street || !config.house) {
7272
console.log(`ℹ️ Skipping DTEK ${config.id}: No address configured.`);
@@ -80,7 +80,6 @@ async function getDtekRegionInfo(browser, config) {
8080
try {
8181
console.log(`🌍 Visiting DTEK ${config.id} (Attempt ${attempt}/${MAX_RETRIES})...`);
8282

83-
// Створюємо контекст з реалістичним User-Agent
8483
const context = await browser.newContext({
8584
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
8685
locale: 'uk-UA'
@@ -91,43 +90,55 @@ async function getDtekRegionInfo(browser, config) {
9190
// Збільшуємо таймаут навігації до 60 сек
9291
await page.goto(config.url, { waitUntil: "domcontentloaded", timeout: 60000 });
9392

94-
// ⚠️ ВАЖЛИВО: Чекаємо 5 секунд, щоб сайт встиг зробити всі редіректи/перезавантаження
93+
// Чекаємо, щоб провантажилися скрипти і можливі поп-апи
9594
await sleep(5000);
9695

97-
// --- Перевірка на екстрені відключення (SMART GLOBAL CHECK) ---
96+
// --- Перевірка на екстрені відключення (SMART GLOBAL CHECK v2) ---
9897
const isEmergency = await page.evaluate(() => {
9998
try {
99+
// Збираємо текст з: 1) Стандартного банера 2) Модальних вікон (Bootstrap/Popups)
100+
let fullText = "";
101+
100102
const attentionBlock = document.querySelector('.m-attention__text');
101-
if (!attentionBlock) return false;
102-
const text = attentionBlock.innerText.toLowerCase();
103+
if (attentionBlock) fullText += " " + attentionBlock.innerText;
104+
105+
// 🔥 ШУКАЄМО ТЕКСТ У МОДАЛКАХ (для Одеси та інших)
106+
const modals = document.querySelectorAll('.modal-content, .popup-content, [role="dialog"], .modal-body');
107+
modals.forEach(m => {
108+
// Беремо текст, якщо елемент існує і хоч трохи схожий на видимий
109+
if (m.innerText) fullText += " " + m.innerText;
110+
});
111+
112+
const text = fullText.toLowerCase();
113+
114+
// Якщо тексту немає - все добре
115+
if (!text.trim()) return false;
103116

104117
// 1. Якщо написано "скасовано" або "відновлено" - це не аварія
105118
if (text.includes("скасовано") || text.includes("відновлено") || text.includes("повертаємось до графіків")) {
106119
return false;
107120
}
108121

109-
// 2. Чи є взагалі слова про відключення?
110-
const hasKeywords = text.includes("екстрені") || text.includes("аварійні");
122+
// 2. Чи є ключові слова?
123+
// Додано "обмеження" для кейсів типу "мережеві обмеження"
124+
const hasKeywords = text.includes("екстрені") || text.includes("аварійні") || text.includes("обмеження");
111125
if (!hasKeywords) return false;
112126

113-
// 3. ФІЛЬТР: Чи це ГЛОБАЛЬНА аварія?
114-
// Якщо є слово "Укренерго" - це майже завжди розпорядження на всю область/країну.
127+
// 3. ФІЛЬТР: Глобально чи локально?
115128
if (text.includes("укренерго")) return true;
116129

117-
// Якщо згадуються локальні маркери - це ЛОКАЛЬНА аварія, ігноруємо її.
118-
// (Якщо ДТЕК пише "в Бориспільському районі", "в частині громади" тощо)
130+
// Перевірка локальних маркерів
119131
if (text.includes("районі") || text.includes("громаді") || text.includes("частині") || text.includes("населеному пункті")) {
120-
// ⚠️ ВИНЯТОК: Якщо при цьому згадується саме обласний центр - це все ж таки важливо!
121-
// Наприклад: "в Одеському районі, зокрема в Одесі"
132+
// ВИНЯТОК: Якщо згадано обласний центр (наприклад, "в Одеському районі, зокрема в Одесі") - це важливо
122133
const mentionsMajorCity = text.includes("київ") || text.includes("києв") ||
123134
text.includes("одес") || text.includes("дніпр");
124135

125136
if (!mentionsMajorCity) {
126-
return false;
137+
return false; // Це локальна аварія десь в селі, ігноруємо
127138
}
128139
}
129140

130-
// Якщо слів-маркерів локальності немає, а слова "екстрені/аварійні" є - вважаємо глобальною.
141+
// Якщо слів-маркерів локальності немає, а тригери є - вважаємо глобальною
131142
return true;
132143
} catch (e) { return false; }
133144
}).catch(() => false);
@@ -136,7 +147,16 @@ async function getDtekRegionInfo(browser, config) {
136147
console.log(`⚠️ DETECTED GLOBAL EMERGENCY for ${config.id}`);
137148
}
138149

139-
// Чекаємо на CSRF токен (ознака того, що сторінка стабільна)
150+
// Спроба закрити модалку, щоб вона не блокувала отримання токенів (опціонально)
151+
try {
152+
await page.evaluate(() => {
153+
const closeBtn = document.querySelector('.modal .close, [data-dismiss="modal"], .btn-close');
154+
if (closeBtn) closeBtn.click();
155+
});
156+
await sleep(1000);
157+
} catch(e) {}
158+
159+
// Чекаємо на CSRF токен
140160
const csrfTokenTag = await page.waitForSelector('meta[name="csrf-token"]', { state: "attached", timeout: 15000 });
141161
const csrfToken = await csrfTokenTag.getAttribute("content");
142162

@@ -164,19 +184,17 @@ async function getDtekRegionInfo(browser, config) {
164184
{ city: config.city, street: config.street, house: config.house, csrfToken }
165185
);
166186

167-
await context.close(); // Закриваємо контекст чисто
187+
await context.close();
168188
return { ...info, emergency: isEmergency };
169189

170190
} catch (error) {
171191
console.warn(`⚠️ Error scraping DTEK ${config.id}: ${error.message}`);
172-
173192
if (page) await page.close().catch(() => { });
174193

175194
if (attempt === MAX_RETRIES) {
176195
console.error(`❌ Failed DTEK ${config.id} giving up.`);
177196
return null;
178197
}
179-
// Чекаємо довше перед наступною спробою
180198
await sleep(5000 + (attempt * 2000));
181199
}
182200
}
@@ -241,7 +259,7 @@ function transformToSvitloFormat(dtekRaw) {
241259
case "no": val00 = 2; val30 = 2; break;
242260
case "first": val00 = 2; val30 = 1; break;
243261
case "second": val00 = 1; val30 = 2; break;
244-
default: val00 = 1; val30 = 1;
262+
case "default": val00 = 1; val30 = 1;
245263
}
246264
scheduleMap[groupKey][dateStr][`${hh}:00`] = val00;
247265
scheduleMap[groupKey][dateStr][`${hh}:30`] = val30;
@@ -303,7 +321,7 @@ function transformYasnoFormat(yasnoRaw) {
303321

304322
// 4. ГОЛОВНИЙ ЗАПУСК
305323
async function run() {
306-
console.log("🚀 Starting Multi-Region Scraper (Robust Mode)...");
324+
console.log("🚀 Starting Multi-Region Scraper (Robust Mode with Odesa Fix)...");
307325

308326
const browser = await chromium.launch({ headless: true });
309327
const processedRegions = [];
@@ -316,15 +334,12 @@ async function run() {
316334
const rawInfo = await getDtekRegionInfo(browser, config);
317335
if (rawInfo) {
318336
const cleanSchedule = transformToSvitloFormat(rawInfo);
319-
320-
// --- ⬇️ ОНОВЛЕНА ЛОГІКА ТУТ ⬇️ ---
321337
const hasSchedule = Object.keys(cleanSchedule).length > 0;
322338

323339
// Додаємо регіон, якщо Є графік АБО Є аварійний режим
324340
if (hasSchedule || rawInfo.emergency) {
325341
console.log(`✅ Success DTEK: ${config.id} (Emergency: ${rawInfo.emergency})`);
326342

327-
// Оновлюємо дати тільки якщо є реальний графік
328343
if (hasSchedule) {
329344
updateGlobalDates(cleanSchedule, globalDates);
330345
}
@@ -334,13 +349,12 @@ async function run() {
334349
name_ua: config.name_ua,
335350
name_ru: config.name_ru,
336351
name_en: config.name_en,
337-
schedule: cleanSchedule, // Може бути пустим {}, якщо emergency=true
352+
schedule: cleanSchedule,
338353
emergency: rawInfo.emergency || false
339354
});
340355
} else {
341356
console.log(`ℹ️ Skipping DTEK ${config.id}: No schedule and no emergency detected.`);
342357
}
343-
// --- ⬆️ КІНЕЦЬ ЗМІН ⬆️ ---
344358
}
345359
}
346360
} catch (err) {

0 commit comments

Comments
 (0)