@@ -66,7 +66,7 @@ function getKyivDate(offsetDays = 0) {
6666// Функція паузи
6767const sleep = ( ms ) => new Promise ( r => setTimeout ( r , ms ) ) ;
6868
69- // 1. ДТЕК (Playwright) - МАКСИМАЛЬНО НАДІЙНА ВЕРСІЯ
69+ // 1. ДТЕК (Playwright) - ВИПРАВЛЕНА ВЕРСІЯ
7070async 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. ГОЛОВНИЙ ЗАПУСК
305323async 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