import { chromium } from 'playwright'; import log from 'electron-log'; import tabsPkg from './common/tabs.js'; const { preparePage, safeDisconnectBrowser } = tabsPkg; const parseDateInput = (dateStr) => { if (!dateStr) return null; if (typeof dateStr === 'number' && Number.isFinite(dateStr)) return new Date(dateStr); if (dateStr instanceof Date && !Number.isNaN(dateStr.getTime())) return dateStr; const raw = String(dateStr).trim(); const ymdMatch = raw.match(/(\d{4}-\d{2}-\d{2})/); if (ymdMatch) return new Date(`${ymdMatch[1]}T00:00:00`); const mmddMatch = raw.match(/(\d{2}-\d{2})/); if (mmddMatch) { const year = new Date().getFullYear(); return new Date(`${year}-${mmddMatch[1]}T00:00:00`); } return null; }; const formatMMDD = (d) => { const mm = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); return `${mm}-${dd}`; }; const buildDateList = (startDate, endDate) => { const start = parseDateInput(startDate); const end = parseDateInput(endDate || startDate); if (!start || !end) return []; const from = start <= end ? start : end; const to = start <= end ? end : start; const dates = []; const cur = new Date(from.getTime()); while (cur <= to) { dates.push(formatMMDD(cur)); cur.setDate(cur.getDate() + 1); } return dates; }; const findHeaderDateIndex = async (container, mmdd) => { for (let attempt = 0; attempt < 10; attempt++) { const headerCells = container.locator( "xpath=.//div[contains(@class,'headerRow')]/div[contains(@class,'headerContent')]", ); const count = await headerCells.count(); for (let i = 0; i < count; i++) { const text = (await headerCells.nth(i).innerText()).replace(/\s+/g, ' ').trim(); if (text.includes(mmdd)) return i; } await container.evaluate((el) => { el.scrollLeft += 600; }); await container.page().waitForTimeout(120); } return -1; }; const toggleRoomByDateIndex = async (container, { roomType, dateIndex, operation }) => { const roomAnchor = container.getByText(roomType, { exact: true }).first(); await roomAnchor.scrollIntoViewIfNeeded(); const row = roomAnchor.locator( "xpath=ancestor::*[.//div[contains(@class,'boardRow')]][1]", ); const boardRow = row.locator("xpath=.//div[contains(@class,'boardRow')]").first(); const boardColPosition = dateIndex + 2; const dayColumn = boardRow.locator(`xpath=./div[${boardColPosition}]`); const targetStateClass = operation === 'open' ? 'error' : 'success'; const expectedCurrentLabel = operation === 'open' ? '满' : '有'; const barByLabel = dayColumn .locator( `xpath=.//*[ (contains(@class,'success') or contains(@class,'error')) and .//*[contains(normalize-space(),'${expectedCurrentLabel}')] ]//*[contains(@class,'bar')]`, ) .first(); const barByClass = dayColumn .locator(`xpath=.//*[contains(@class,'${targetStateClass}')]//*[contains(@class,'bar')]`) .first(); const bar = (await barByLabel.count()) > 0 ? barByLabel : barByClass; await bar.waitFor({ state: 'visible' }); await bar.scrollIntoViewIfNeeded(); await bar.click(); }; (async () => { let browser; try { const prepared = await preparePage(chromium); browser = prepared.browser; const page = prepared.page; const targetUrl = 'https://hotel.fliggy.com/ebooking/hotelBaseInfoUv.htm#/ebk/homeV1'; const currentUrl = page.url(); if (!currentUrl || !currentUrl.startsWith(targetUrl)) { await page.goto(targetUrl); } await page.waitForTimeout(4000 + Math.random() * 300); await page.getByRole('menuitem', { name: '房价房量管理' }).click(); await page.waitForTimeout(4000 + Math.random() * 1000); await page.getByText('房价房量日历').click(); const roomType = process.env.ROOM_TYPE; const startDate = process.env.START_DATE; const endDate = process.env.END_DATE; const operation = process.env.OPERATION === 'close' ? 'close' : 'open'; if (!roomType || !startDate) { log.info('ROOM_TYPE/START_DATE not provided, skip room toggle.'); return; } const container = page.locator('#price-reserve-table-container'); await container.waitFor({ state: 'visible' }); const dateList = buildDateList(startDate, endDate); for (const mmdd of dateList) { const dateIndex = await findHeaderDateIndex(container, mmdd); if (dateIndex < 0) { throw new Error(`Date not found in header: ${mmdd}`); } await toggleRoomByDateIndex(container, { roomType, dateIndex, operation }); await page.waitForTimeout(600 + Math.random() * 600); } } catch (error) { log.error(error); process.exitCode = 1; }finally { await safeDisconnectBrowser(browser); } })();