Fügt Unterstützung für die neue nuscore API hinzu. Aktualisiert die Backend-Routen zur Verarbeitung von Anfragen an die nuscore API und integriert die neuen Dialogkomponenten im Frontend. Ermöglicht das Erstellen lokaler Kopien von nuscore-Daten und verbessert die Benutzeroberfläche durch neue Schaltflächen und Dialoge. Entfernt veraltete Konsolenausgaben und optimiert die Logik zur PIN-Verwaltung.

This commit is contained in:
Torsten Schulz (local)
2025-10-03 15:57:57 +02:00
parent cc964da9cf
commit 4b1a046149
28 changed files with 4325 additions and 6072 deletions

View File

@@ -0,0 +1,193 @@
import express from 'express';
import fetch from 'node-fetch';
const router = express.Router();
// Cookie-Store für nuscore-Sessions
const cookieStore = new Map();
// Hilfsfunktion zum Extrahieren von Cookies aus Set-Cookie Headers
function extractCookies(setCookieHeaders) {
const cookies = {};
if (setCookieHeaders) {
setCookieHeaders.forEach(cookie => {
const [nameValue] = cookie.split(';');
const [name, value] = nameValue.split('=');
if (name && value) {
cookies[name.trim()] = value.trim();
}
});
}
return cookies;
}
// Hilfsfunktion zum Formatieren von Cookies für Requests
function formatCookies(cookies) {
return Object.entries(cookies)
.map(([name, value]) => `${name}=${value}`)
.join('; ');
}
// Meeting-Info API-Endpunkt
router.get('/meetinginfo/:code', async (req, res) => {
const { code } = req.params;
console.log(`📊 Meeting-Info API für Code: ${code}`);
try {
// Hole Cookies für diesen Code (falls vorhanden)
const cookies = cookieStore.get(code) || {};
const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${code}/meetinginfo`;
const response = await fetch(url, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Referer': 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Connection': 'keep-alive',
...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) })
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Speichere neue Cookies falls vorhanden
const newCookies = extractCookies(response.headers.raw()['set-cookie']);
if (Object.keys(newCookies).length > 0) {
cookieStore.set(code, { ...cookies, ...newCookies });
console.log(`🍪 Cookies für Code ${code} gespeichert:`, Object.keys(newCookies));
}
// CORS-Header setzen
res.set({
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.json(data);
console.log(`✅ Meeting-Info für Code ${code} erfolgreich abgerufen`);
} catch (error) {
console.error(`❌ Fehler beim Abrufen der Meeting-Info für Code ${code}:`, error);
res.status(500).json({
error: 'Fehler beim Abrufen der Meeting-Info',
details: error.message
});
}
});
// Cookie-Initialisierung (für den ersten Request)
router.post('/init-cookies/:code', async (req, res) => {
const { code } = req.params;
console.log(`🍪 Cookie-Initialisierung für Code: ${code}`);
try {
// Erster Request an die nuscore-Seite um Cookies zu erhalten
const response = await fetch(`https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${code}`, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Connection': 'keep-alive'
}
});
// Extrahiere Cookies
const cookies = extractCookies(response.headers.raw()['set-cookie']);
if (Object.keys(cookies).length > 0) {
cookieStore.set(code, cookies);
console.log(`🍪 Cookies für Code ${code} initialisiert:`, Object.keys(cookies));
}
res.json({
success: true,
cookies: Object.keys(cookies),
message: `Cookies für Code ${code} initialisiert`
});
} catch (error) {
console.error(`❌ Fehler bei Cookie-Initialisierung für Code ${code}:`, error);
res.status(500).json({
error: 'Fehler bei Cookie-Initialisierung',
details: error.message
});
}
});
// Detaillierte Meeting-Daten API-Endpunkt
router.get('/meetingdetails/:uuid', async (req, res) => {
const { uuid } = req.params;
console.log(`📊 Meeting-Details API für UUID: ${uuid}`);
try {
// Hole Cookies für diesen Code (falls vorhanden)
const cookies = cookieStore.get(uuid) || {};
const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${uuid}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Referer': 'https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Connection': 'keep-alive',
...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) })
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Speichere neue Cookies falls vorhanden
const newCookies = extractCookies(response.headers.raw()['set-cookie']);
if (Object.keys(newCookies).length > 0) {
cookieStore.set(uuid, { ...cookies, ...newCookies });
console.log(`🍪 Cookies für UUID ${uuid} gespeichert:`, Object.keys(newCookies));
}
// CORS-Header setzen
res.set({
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.json(data);
console.log(`✅ Meeting-Details für UUID ${uuid} erfolgreich abgerufen`);
} catch (error) {
console.error(`❌ Fehler beim Abrufen der Meeting-Details für UUID ${uuid}:`, error);
res.status(500).json({
error: 'Fehler beim Abrufen der Meeting-Details',
details: error.message
});
}
});
export default router;

View File

@@ -0,0 +1,78 @@
import express from 'express';
import nuscoreProxyService from '../services/nuscoreProxyService.js';
const router = express.Router();
// Hauptroute für nuscore-Seite
router.get('/', async (req, res) => {
try {
const { code, pin } = req.query;
if (!code) {
return res.status(400).json({
error: 'Code-Parameter ist erforderlich'
});
}
console.log(`📊 Proxy-Anfrage für Code: ${code}, PIN: ${pin || 'nicht angegeben'}`);
const html = await nuscoreProxyService.proxyNuscorePage(code, pin);
// CORS-Header setzen für iframe-Einbettung
res.set({
'Content-Type': 'text/html; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'X-Frame-Options': 'ALLOWALL',
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.send(html);
} catch (error) {
console.error('❌ Proxy-Fehler:', error);
res.status(500).json({
error: 'Fehler beim Laden der nuscore-Seite',
details: error.message
});
}
});
// Asset-Proxy für CSS, JS, Bilder etc.
router.get('/assets/*', async (req, res) => {
try {
const assetPath = req.params[0];
const originalUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/${assetPath}`;
console.log(`📦 Lade Asset: ${originalUrl}`);
const { content, contentType } = await nuscoreProxyService.proxyAsset(originalUrl);
res.set({
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'public, max-age=3600'
});
res.send(content);
} catch (error) {
console.error(`❌ Asset-Fehler für ${req.params[0]}:`, error.message);
res.status(404).json({
error: 'Asset nicht gefunden',
path: req.params[0]
});
}
});
// Health-Check Route
router.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'nuscore-proxy',
timestamp: new Date().toISOString()
});
});
export default router;

View File

@@ -0,0 +1,140 @@
import express from 'express';
import fetch from 'node-fetch';
const router = express.Router();
// Einfacher HTTP-Proxy ohne Playwright
router.get('/', async (req, res) => {
try {
const { code, pin } = req.query;
if (!code) {
return res.status(400).json({
error: 'Code-Parameter ist erforderlich'
});
}
console.log(`📊 Einfacher Proxy für Code: ${code}, PIN: ${pin || 'nicht angegeben'}`);
// Direkte HTTP-Anfrage an nuscore
const nuscoreUrl = `https://ttde-apps.liga.nu/nuliga/nuscore-tt/meetings-list?code=${encodeURIComponent(code)}`;
const response = await fetch(nuscoreUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
},
timeout: 30000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
let html = await response.text();
// Einfache HTML-Modifikationen
html = html
.replace(/<meta[^>]*http-equiv=["']content-security-policy["'][^>]*>/gi, '')
.replace(/<meta[^>]*http-equiv=["']x-frame-options["'][^>]*>/gi, '')
.replace(/<meta[^>]*http-equiv=["']x-content-type-options["'][^>]*>/gi, '');
// Entferne Service Worker
html = html.replace(
/navigator\.serviceWorker\.register\([^)]+\)/g,
'// Service Worker deaktiviert für Proxy'
);
// Korrigiere alle Asset-URLs zu absoluten URLs
html = html.replace(
/src="\.\//g,
'src="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
html = html.replace(
/href="\.\//g,
'href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
// Korrigiere auch URLs die bereits auf localhost zeigen
html = html.replace(
/src="http:\/\/localhost:3000\/nuliga\/nuscore-tt\//g,
'src="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
html = html.replace(
/href="http:\/\/localhost:3000\/nuliga\/nuscore-tt\//g,
'href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/'
);
// Entferne vorhandene base-Tags und füge unseres hinzu
html = html.replace(
/<base[^>]*>/gi,
''
);
// Füge base-Tag hinzu für korrekte Asset-Auflösung
html = html.replace(
'<head>',
'<head><base href="https://ttde-apps.liga.nu/nuliga/nuscore-tt/">'
);
// Debug: Logge die HTML-Struktur
console.log('🔍 HTML nach base-Tag:', html.substring(html.indexOf('<base'), html.indexOf('</head>') + 7));
// Füge PIN-Injektion hinzu falls PIN vorhanden
if (pin) {
const pinScript = `
<script>
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
const pinInput = document.querySelector('input[type="password"][placeholder*="PIN"], input[placeholder*="Pin"], input[placeholder*="pin"]');
if (pinInput) {
pinInput.value = '${pin}';
pinInput.dispatchEvent(new Event('input', { bubbles: true }));
pinInput.dispatchEvent(new Event('change', { bubbles: true }));
console.log('PIN automatisch eingefügt: ${pin}');
}
}, 2000);
});
</script>
`;
html = html.replace('</head>', `${pinScript}</head>`);
}
// CORS-Header setzen für iframe-Einbettung
res.set({
'Content-Type': 'text/html; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'X-Frame-Options': 'ALLOWALL',
'Content-Security-Policy': "frame-ancestors *;",
'Cache-Control': 'no-cache, no-store, must-revalidate'
});
res.send(html);
console.log('✅ Einfacher Proxy erfolgreich');
} catch (error) {
console.error('❌ Einfacher Proxy-Fehler:', error);
res.status(500).json({
error: 'Fehler beim Laden der nuscore-Seite',
details: error.message
});
}
});
// Health-Check Route
router.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'nuscore-simple-proxy',
timestamp: new Date().toISOString()
});
});
export default router;