feat: MakerWorld Debug-Screenshot + bessere Fehlerdiagnose bei fehlgeschlagenem Abruf

This commit is contained in:
2026-07-11 02:28:48 +02:00
parent 59cced35be
commit ae13d1b239
2 changed files with 62 additions and 8 deletions

View File

@@ -130,7 +130,16 @@ async function getNextDataFromPage(page) {
}
async function isCloudflareChallenge(page) {
const title = await page.title().catch(() => '');
return /just a moment|checking your browser|attention required/i.test(title);
return /just a moment|checking your browser|attention required|einen moment|überprüfung|einen augenblick|verifizierung ihres browsers/i.test(title);
}
// Speichert einen Screenshot + die aktuelle URL/Titel zur Fehlerdiagnose
async function saveDebugScreenshot(userId, page) {
const dir = `${PROFILE_BASE_DIR}/${userId}`;
fs.mkdirSync(dir, { recursive: true });
await page.screenshot({ path: `${dir}/last-failure.png`, fullPage: false });
fs.writeFileSync(`${dir}/last-failure.json`, JSON.stringify({
url: page.url(), title: await page.title().catch(() => '?'), at: new Date().toISOString(),
}));
}
function isLoginUrl(url) {
return /bambulab\.com\/[^/]*\/?sign-in|makerworld\.com\/[a-z]{2}\/sign-in/i.test(url || '');
@@ -155,7 +164,8 @@ async function attemptLogin(userId, browser, page) {
]);
const pwField = await findFirstSelector(page, ['input[type="password"]']);
if (!emailField || !pwField) {
throw new Error('Login-Formular nicht gefunden (Selektoren evtl. veraltet)');
await saveDebugScreenshot(userId, page).catch(() => {});
throw new Error(`Login-Formular nicht gefunden (Selektoren evtl. veraltet) — Seite: "${await page.title().catch(()=>'?')}" (${page.url()})`);
}
await page.type(emailField, creds.username, { delay: 25 });
@@ -192,6 +202,7 @@ async function attemptLogin(userId, browser, page) {
throw err;
}
await saveDebugScreenshot(userId, page).catch(() => {});
throw new Error('Login fehlgeschlagen (weder Daten noch Code-Feld erkannt) — evtl. falsche Zugangsdaten oder geändertes Seitenlayout');
}
@@ -205,14 +216,17 @@ async function fetchStatsViaBrowserInner(userId, forceRefresh) {
}
let browser;
let page;
try {
browser = await launchBrowser(userId);
const page = await browser.newPage();
page = await browser.newPage();
await page.setUserAgent(USER_AGENT);
await page.setViewport({ width: 1366, height: 900 });
await page.goto(DATA_URL, { waitUntil: 'networkidle2', timeout: 45000 });
for (let i = 0; i < 3 && await isCloudflareChallenge(page); i++) {
// Bis zu 20 Sekunden Zeit geben, falls Cloudflare eine automatische
// Managed-Challenge zeigt (löst sich bei echtem Chromium meist selbst)
for (let i = 0; i < 5 && await isCloudflareChallenge(page); i++) {
await new Promise(r => setTimeout(r, 4000));
}
@@ -223,7 +237,10 @@ async function fetchStatsViaBrowserInner(userId, forceRefresh) {
}
if (!parsed) {
throw new Error('__NEXT_DATA__ nicht gefunden (weder Daten noch erkennbare Login-/Challenge-Seite) — Seitenlayout evtl. geändert');
const debugTitle = await page.title().catch(() => '?');
const debugUrl = page.url();
await saveDebugScreenshot(userId, page).catch(() => {});
throw new Error(`__NEXT_DATA__ nicht gefunden — Seite: "${debugTitle}" (${debugUrl}). Debug-Screenshot verfügbar über GET /debug-screenshot`);
}
const data = mapStatisticalData(parsed);
@@ -325,15 +342,26 @@ router.get('/login-status', authenticate, (req, res) => {
const userId = req.user.id;
const cache = getCache(userId);
const creds = getCredentials(userId);
const debugPath = `${PROFILE_BASE_DIR}/${userId}/last-failure.json`;
let debugInfo = null;
try { debugInfo = JSON.parse(fs.readFileSync(debugPath, 'utf8')); } catch {}
res.json({
configured: !!creds,
username: creds?.username || null,
lastFetchedAt: cache.fetchedAt || null,
lastError: cache.error || null,
needsCode: pendingLoginByUser.has(userId),
debugScreenshot: debugInfo,
});
});
// Letzter Fehl-Screenshot (Diagnose) — nur der eigene
router.get('/debug-screenshot', authenticate, (req, res) => {
const path = `${PROFILE_BASE_DIR}/${req.user.id}/last-failure.png`;
if (!fs.existsSync(path)) return res.status(404).json({ error: 'Kein Debug-Screenshot vorhanden' });
res.sendFile(path);
});
router.post('/credentials', authenticate, async (req, res) => {
const { username, password } = req.body;
if (!username || !password) return res.status(400).json({ error: 'Benutzername und Passwort erforderlich' });