Files
Whatsapp-Bridge-Addon/whatsapp_bridge/server.js

179 lines
5.6 KiB
JavaScript

const { Client, LocalAuth } = require('whatsapp-web.js');
const express = require('express');
const qrcode = require('qrcode');
const fs = require('fs-extra');
const path = require('path');
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'ui')));
let lastQr = "";
let isReady = false;
let isInitializing = true;
const SESSION_DATA = '/data/auth';
// --- AUTOMATISCHE SELBSTREINIGUNG BEIM START ---
// Löscht alte Chromium-Sperrdateien, die das "LADE CLIENT..."-Hängen verursachen
try {
const lockFolder = path.join(SESSION_DATA, 'session-ha_bridge_session');
const lockFile = path.join(lockFolder, 'SingletonLock');
if (fs.existsSync(lockFile)) {
fs.removeSync(lockFile);
console.log('🔒 Blockierendes Chromium-SingletonLock wurde automatisch entfernt!');
}
} catch (e) {
console.log('Info: Keine blockierende Sperrdatei beim Start gefunden.');
}
async function handleCrash(errorContext) {
console.error(`💥 CRITICAL ERROR CAUGHT: ${errorContext}`);
isReady = false;
try {
console.log('Versuche Browser sauber zu schließen...');
if (client && client.pupBrowser) {
await client.pupBrowser.close().catch(() => {});
}
} catch (e) {
console.error('Browser-Close fehlgeschlagen:', e);
}
console.log('Add-on wird beendet. HA Watchdog startet es neu...');
process.exit(1);
}
process.on('uncaughtException', async (err) => {
if (err.message.includes('ProtocolError') || err.message.includes('Runtime.callFunctionOn') || err.message.includes('Target closed')) {
await handleCrash('Puppeteer Protocol Timeout / Crash');
} else {
console.error('Uncaught Exception:', err);
}
});
process.on('unhandledRejection', async (reason, promise) => {
if (reason && reason.message && reason.message.includes('Session closed')) {
await handleCrash('Unhandled Puppeteer Session Rejection');
} else {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
}
});
const client = new Client({
authStrategy: new LocalAuth({
dataPath: SESSION_DATA,
clientId: "ha_bridge_session"
}),
authTimeoutMs: 90000,
qrMaxRetries: 5,
puppeteer: {
executablePath: '/usr/bin/chromium-browser',
protocolTimeout: 0,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--disable-extensions'
// '--single-process' und '--no-zygote' wurden entfernt, da sie in Alpine 3.19 Hangs verursachen können!
]
}
});
client.on('qr', (qr) => {
lastQr = qr;
isReady = false;
isInitializing = false;
console.log('Neuer QR Code generiert.');
});
client.on('ready', () => {
isReady = true;
lastQr = "";
isInitializing = false;
console.log('WhatsApp ist bereit!');
});
client.on('disconnected', async (reason) => {
console.log(`WhatsApp Verbindung getrennt (Grund: ${reason}).`);
await handleCrash('Externer Disconnect');
});
// --- ROUTES ---
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'ui', 'index.html'));
});
app.get('/api/status', async (req, res) => {
try {
let qrData = null;
if (lastQr) {
qrData = await qrcode.toDataURL(lastQr);
}
let clientState = 'DISCONNECTED';
if (isReady) {
clientState = await client.getState().catch(() => 'ERROR');
}
res.json({
isReady: isReady,
hasQr: lastQr !== "",
qrCode: qrData,
isInitializing: isInitializing,
clientState: clientState
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// NOTFALL-SICHER: Löscht die Session radikal, selbst wenn der Client eingefroren ist!
app.post('/auth/logout', async (req, res) => {
try {
console.log("Logout erzwungen über Web-UI...");
if (client) {
await client.logout().catch(() => {});
await client.destroy().catch(() => {});
}
await fs.remove(SESSION_DATA).catch(() => {});
res.json({ success: true, message: "Session radikal gelöscht. Addon startet jetzt sauber neu..." });
setTimeout(() => process.exit(0), 1500);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// NOTFALL-RESET: Putzt das komplette Verzeichnis leer
app.delete('/system/reset', async (req, res) => {
try {
console.log("Factory Reset erzwungen...");
if (client) await client.destroy().catch(() => {});
await fs.emptyDir('/data').catch(() => {});
res.json({ success: true, message: "Werkseinstellungen erzwungen. Alles gelöscht. Neustart..." });
setTimeout(() => process.exit(0), 1500);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/send', async (req, res) => {
const { number, message } = req.body;
if (!isReady) return res.status(503).json({ error: 'Client nicht bereit' });
try {
const chatId = number.includes('@c.us') ? number : `${number}@c.us`;
await client.sendMessage(chatId, message);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
console.log('Initialisiere WhatsApp Client...');
client.initialize().catch(async (err) => {
await handleCrash(`Initialisierungsfehler: ${err.message}`);
});
app.listen(3000, () => console.log('WhatsApp Bridge Server läuft auf Port 3000'));