fix(edu): read diary event times directly from DOM, drop 25 AJAX clicks
ci / validate (push) Successful in 6s
ci / build (push) Has been skipped
ci / lint-prettier (push) Successful in 12s
ci / lint-ruff (push) Successful in 5s
ci / lint-yaml (push) Successful in 8s
ci / lint-dockerfiles (push) Successful in 5s
ci / deploy-userbot-panel (push) Has been skipped

_collect_event_times re-clicked every a.event-link and waited ~3s per
event for a visible span.data, but the calendar embeds all times in
div.event-full-info[data-event-full-info-id] span.data already. The old
loop took ~109s for 25 events and collected 0 (original divs stay
sf-hidden), effectively hanging /diary. Now a single evaluate reads all
times (~3.7s), parsing HH:MM from p.date span.data.
This commit is contained in:
2026-09-10 00:31:04 +02:00
parent dad9cf2104
commit 68630eb773
+27 -56
View File
@@ -555,69 +555,40 @@ def _parse_calendar_html(table_html: str) -> tuple:
async def _collect_event_times(page) -> dict:
"""Click every calendar `a.event-link` and read the time from the AJAX popup.
"""Read event times straight from the rendered calendar DOM.
The site shows event details only after a click fills the fancybox container
(`div.event-full-info span.data`). Returns {event_id: 'HH:MM'} for events
that have a date; events without one are simply skipped. A failed click on a
single event never breaks the whole diary parse.
The diary page embeds `div.event-full-info[data-event-full-info-id]`
containing `span.data` (e.g. "2026-09-02 16:30:00") for every event, so no
AJAX popup clicks are needed. Returns {event_id: 'HH:MM'} for events that
have a date; events without one are simply skipped.
"""
times_by_id: dict[str, str] = {}
try:
links = await page.query_selector_all('a.event-link')
raw_times = await page.evaluate(
"""() => {
const out = {};
for (const div of document.querySelectorAll(
'div.event-full-info[data-event-full-info-id]'
)) {
const id = div.getAttribute('data-event-full-info-id');
const date_span = div.querySelector('p.date span.data');
if (id && date_span) {
out[id] = date_span.textContent.trim();
}
}
return out;
}"""
)
except Exception as e:
logger.warning(f'Failed to list diary event links: {e}')
logger.warning(f'Failed to read diary event times from DOM: {e}')
return times_by_id
for link in links:
event_id = None
event_deadline = time.monotonic() + 10
try:
event_id = await link.get_attribute('data-event-id')
if not event_id or event_id in times_by_id:
continue
await link.evaluate('(el) => el.click()')
await page.wait_for_selector(
f'div.event-full-info[data-event-full-info-id="{event_id}"] span.data',
timeout=3000,
state='visible',
)
if time.monotonic() > event_deadline:
break
time_text = await page.evaluate(
"""(id) => {
const nodes = document.querySelectorAll(
'div.event-full-info[data-event-full-info-id="' + id + '"] span.data'
);
for (const el of nodes) {
if (el.getClientRects().length > 0 || el.offsetParent !== null) {
return el.textContent.trim();
}
}
return null;
}""",
event_id,
)
if time_text:
m = re.search(r'(\d{1,2}:\d{2})', time_text)
if m:
times_by_id[event_id] = m.group(1)
except Exception as e:
logger.debug(f'Failed to read time for diary event {event_id}: {e}')
if time.monotonic() > event_deadline:
break
finally:
# Close the fancybox popup (click the close button, else Escape).
closed = False
with contextlib.suppress(Exception):
close_btn = await page.query_selector('#fancybox-close')
if close_btn is not None:
await close_btn.click(timeout=2000)
closed = True
if not closed:
with contextlib.suppress(Exception):
await page.keyboard.press('Escape')
await page.wait_for_timeout(150)
for event_id, time_text in (raw_times or {}).items():
if not time_text:
continue
m = re.search(r'(\d{1,2}:\d{2})', time_text)
if m:
times_by_id[event_id] = m.group(1)
logger.info(f'Diary event times collected for {len(times_by_id)} events')
return times_by_id