update
This commit is contained in:
@@ -14,6 +14,7 @@ import MessageReactions from './MessageReactions';
|
|||||||
interface ChatMessageProps {
|
interface ChatMessageProps {
|
||||||
message: NextcloudMessage;
|
message: NextcloudMessage;
|
||||||
isOwnMessage: boolean;
|
isOwnMessage: boolean;
|
||||||
|
isOneToOne?: boolean;
|
||||||
onReplyClick?: (message: NextcloudMessage) => void;
|
onReplyClick?: (message: NextcloudMessage) => void;
|
||||||
onReactionToggled?: (messageId: number) => void;
|
onReactionToggled?: (messageId: number) => void;
|
||||||
reactionsOverride?: { reactions: Record<string, number>; reactionsSelf: string[] };
|
reactionsOverride?: { reactions: Record<string, number>; reactionsSelf: string[] };
|
||||||
@@ -28,7 +29,7 @@ function hasPollParam(params?: Record<string, any>): boolean {
|
|||||||
return params?.object?.type === 'talk-poll';
|
return params?.object?.type === 'talk-poll';
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatMessage: React.FC<ChatMessageProps> = ({ message, isOwnMessage, onReplyClick, onReactionToggled, reactionsOverride }) => {
|
const ChatMessage: React.FC<ChatMessageProps> = ({ message, isOwnMessage, isOneToOne, onReplyClick, onReactionToggled, reactionsOverride }) => {
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { chromium, Page, Frame } from '@playwright/test';
|
import { chromium, Page, Frame } from '@playwright/test';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
import {
|
import {
|
||||||
FdiskMember,
|
FdiskMember,
|
||||||
FdiskAusbildung,
|
FdiskAusbildung,
|
||||||
@@ -10,10 +12,27 @@ import {
|
|||||||
const BASE_URL = process.env.FDISK_BASE_URL ?? 'https://app.fdisk.at';
|
const BASE_URL = process.env.FDISK_BASE_URL ?? 'https://app.fdisk.at';
|
||||||
const ID_FEUERWEHREN = process.env.FDISK_ID_FEUERWEHREN ?? '164';
|
const ID_FEUERWEHREN = process.env.FDISK_ID_FEUERWEHREN ?? '164';
|
||||||
const ID_INSTANZEN = process.env.FDISK_ID_INSTANZEN ?? '2853';
|
const ID_INSTANZEN = process.env.FDISK_ID_INSTANZEN ?? '2853';
|
||||||
|
const DEBUG_HTML = process.env.FDISK_DEBUG_HTML === '1' || process.env.FDISK_DEBUG_HTML === 'true';
|
||||||
|
|
||||||
const LOGIN_URL = `${BASE_URL}/fdisk/module/vws/logins/logins.aspx`;
|
const LOGIN_URL = `${BASE_URL}/fdisk/module/vws/logins/logins.aspx`;
|
||||||
const MEMBERS_URL = `${BASE_URL}/fdisk/module/mgvw/mitgliedschaften/meine_Mitglieder.aspx`;
|
const MEMBERS_URL = `${BASE_URL}/fdisk/module/mgvw/mitgliedschaften/meine_Mitglieder.aspx`;
|
||||||
|
|
||||||
|
/** Save frame HTML to debug/ folder when FDISK_DEBUG_HTML=1 */
|
||||||
|
async function dumpHtml(frame: Frame, label: string): Promise<void> {
|
||||||
|
if (!DEBUG_HTML) return;
|
||||||
|
try {
|
||||||
|
const debugDir = path.resolve(process.cwd(), 'debug');
|
||||||
|
fs.mkdirSync(debugDir, { recursive: true });
|
||||||
|
const html = await frame.content();
|
||||||
|
const safeName = label.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||||
|
const filePath = path.join(debugDir, `${safeName}.html`);
|
||||||
|
fs.writeFileSync(filePath, html, 'utf-8');
|
||||||
|
log(` [debug] saved HTML → ${filePath} (${(html.length / 1024).toFixed(1)} KB)`);
|
||||||
|
} catch (err: any) {
|
||||||
|
log(` [debug] failed to save HTML for "${label}": ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function log(msg: string) {
|
function log(msg: string) {
|
||||||
console.log(`[scraper] ${new Date().toISOString()} ${msg}`);
|
console.log(`[scraper] ${new Date().toISOString()} ${msg}`);
|
||||||
}
|
}
|
||||||
@@ -183,6 +202,7 @@ export async function scrapeAll(username: string, password: string, knownStNrs:
|
|||||||
|
|
||||||
const members = await scrapeKnownMembers(mainFrame, knownStNrs, knownNames);
|
const members = await scrapeKnownMembers(mainFrame, knownStNrs, knownNames);
|
||||||
log(`Found ${members.length} members (targeted query)`);
|
log(`Found ${members.length} members (targeted query)`);
|
||||||
|
if (DEBUG_HTML) log(`[debug] HTML dump mode ON — saving pages to debug/`);
|
||||||
|
|
||||||
const ausbildungen: FdiskAusbildung[] = [];
|
const ausbildungen: FdiskAusbildung[] = [];
|
||||||
const befoerderungen: FdiskBefoerderung[] = [];
|
const befoerderungen: FdiskBefoerderung[] = [];
|
||||||
@@ -209,6 +229,9 @@ export async function scrapeAll(username: string, password: string, knownStNrs:
|
|||||||
member.wohnort = profileFields.wohnort;
|
member.wohnort = profileFields.wohnort;
|
||||||
member.plz = profileFields.plz;
|
member.plz = profileFields.plz;
|
||||||
|
|
||||||
|
// Debug: dump the member detail page (Ausbildungen are scraped from here)
|
||||||
|
await dumpHtml(mainFrame, `detail_StNr${member.standesbuchNr}`);
|
||||||
|
|
||||||
// Extract mitgliedschaft + person params from the current URL for constructing sub-section URLs.
|
// Extract mitgliedschaft + person params from the current URL for constructing sub-section URLs.
|
||||||
// PersonenForm.aspx is in the personen module; sub-sections are each in their own module.
|
// PersonenForm.aspx is in the personen module; sub-sections are each in their own module.
|
||||||
// URL pattern: ?search=1&searchid_mitgliedschaften=X&id_personen=Y&id_mitgliedschaften=X&searchid_personen=Y&searchid_maskmode=
|
// URL pattern: ?search=1&searchid_mitgliedschaften=X&id_personen=Y&id_mitgliedschaften=X&searchid_personen=Y&searchid_maskmode=
|
||||||
@@ -729,6 +752,11 @@ async function scrapeAusbildungenFromDetailPage(frame: Frame, member: FdiskMembe
|
|||||||
break; // only process the first Ausbildung table found
|
break; // only process the first Ausbildung table found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug: dump HTML when no Ausbildungen found
|
||||||
|
if (ausbildungen.length === 0) {
|
||||||
|
await dumpHtml(frame, `ausbildungen_StNr${member.standesbuchNr}`);
|
||||||
|
}
|
||||||
|
|
||||||
return ausbildungen;
|
return ausbildungen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,6 +835,12 @@ async function navigateAndGetTableRows(
|
|||||||
|
|
||||||
log(` → ${allRows.length} total rows, ${fdcRows.length} FdcLayList rows, ${dataRows.length} data rows (with date in cells[0])`);
|
log(` → ${allRows.length} total rows, ${fdcRows.length} FdcLayList rows, ${dataRows.length} data rows (with date in cells[0])`);
|
||||||
|
|
||||||
|
// Debug: dump HTML when no data rows found
|
||||||
|
if (dataRows.length === 0) {
|
||||||
|
const urlSlug = url.split('/').pop()?.split('?')[0] ?? 'unknown';
|
||||||
|
await dumpHtml(frame, `navigateAndGetTableRows_${urlSlug}`);
|
||||||
|
}
|
||||||
|
|
||||||
return dataRows;
|
return dataRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1021,6 +1055,7 @@ async function scrapeMemberFahrgenehmigungen(
|
|||||||
|
|
||||||
if (klasseIdx === -1) {
|
if (klasseIdx === -1) {
|
||||||
log(` Fahrgenehmigungen for StNr ${standesbuchNr}: could not determine Klasse column. Returning empty.`);
|
log(` Fahrgenehmigungen for StNr ${standesbuchNr}: could not determine Klasse column. Returning empty.`);
|
||||||
|
await dumpHtml(frame, `fahrgenehmigungen_StNr${standesbuchNr}`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user