255 lines
7.9 KiB
JavaScript
255 lines
7.9 KiB
JavaScript
import express from 'express';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import yaml from 'js-yaml';
|
|
import Emusks from './emusks-local/index.js';
|
|
|
|
// --- GESTION DES CHEMINS (ES Modules) ---
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// --- LECTURE DE LA CONFIGURATION (YAML ou JSON) ---
|
|
let config = {};
|
|
const configPaths = [
|
|
path.join(__dirname, 'config.yml'),
|
|
path.join(__dirname, 'config.yaml'),
|
|
path.join(__dirname, '../config.yml'),
|
|
path.join(process.cwd(), 'config.yml')
|
|
];
|
|
|
|
for (const configPath of configPaths) {
|
|
try {
|
|
if (fs.existsSync(configPath)) {
|
|
const fileContents = fs.readFileSync(configPath, 'utf8');
|
|
config = yaml.load(fileContents);
|
|
console.log(`✅ Configuration chargée depuis : ${configPath}`);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.warn(`⚠️ Erreur lecture config ${configPath}: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
if (Object.keys(config).length === 0) {
|
|
console.warn("⚠️ Aucun fichier de configuration trouvé, utilisation des variables d'environnement.");
|
|
}
|
|
|
|
// --- CONFIGURATION ---
|
|
const API_SECRET = config.api_secret || process.env.API_SECRET || 'secret_par_defaut';
|
|
const PORT = config.port || process.env.PORT || 3000;
|
|
const NODE_ENV = process.env.NODE_ENV || 'development';
|
|
|
|
console.log(`🔧 Environment: ${NODE_ENV}`);
|
|
console.log(`🔐 API Secret configured: ${API_SECRET ? 'Yes' : 'No'}`);
|
|
console.log(`🚀 Port: ${PORT}`);
|
|
|
|
// --- INITIALISATION EXPRESS ---
|
|
const app = express();
|
|
|
|
// Middleware pour parser le JSON (avec limite augmentée si besoin)
|
|
app.use(express.json({ limit: '10mb' }));
|
|
|
|
// Logging des requêtes (seulement en dev)
|
|
if (NODE_ENV === 'development') {
|
|
app.use((req, res, next) => {
|
|
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
|
|
next();
|
|
});
|
|
}
|
|
|
|
// --- ENDPOINT DE SANTÉ (Health Check) ---
|
|
app.get('/health', (req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
service: 'twitter-worker',
|
|
timestamp: new Date().toISOString(),
|
|
environment: NODE_ENV
|
|
});
|
|
});
|
|
|
|
// --- ENDPOINT RACINE ---
|
|
app.get('/', (req, res) => {
|
|
res.json({
|
|
service: 'Twitter Worker API',
|
|
version: '1.0.0',
|
|
endpoints: {
|
|
health: 'GET /health',
|
|
call: 'POST /api/twitter/call'
|
|
}
|
|
});
|
|
});
|
|
|
|
// --- MIDDLEWARE DE SÉCURITÉ ---
|
|
const authMiddleware = (req, res, next) => {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader) {
|
|
return res.status(401).json({
|
|
error: 'Non autorisé. Header Authorization manquant.',
|
|
required: 'Bearer <API_SECRET>'
|
|
});
|
|
}
|
|
|
|
const [scheme, token] = authHeader.split(' ');
|
|
|
|
if (scheme !== 'Bearer' || token !== API_SECRET) {
|
|
console.warn(`⚠️ Tentative d'accès non autorisé depuis ${req.ip}`);
|
|
return res.status(401).json({
|
|
error: 'Non autorisé. Clé API invalide.'
|
|
});
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
// Appliquer l'authentification uniquement sur les routes API
|
|
app.use('/api', authMiddleware);
|
|
|
|
// --- ENDPOINT PROXY DYNAMIQUE ---
|
|
app.post('/api/twitter/call', async (req, res) => {
|
|
const { auth_token, proxy, domain, method, args } = req.body;
|
|
|
|
// Validation des paramètres requis
|
|
if (!auth_token) {
|
|
return res.status(400).json({
|
|
error: "auth_token requis.",
|
|
hint: "Le token d'authentification Twitter est obligatoire."
|
|
});
|
|
}
|
|
|
|
if (!domain || !method) {
|
|
return res.status(400).json({
|
|
error: "domain et method requis.",
|
|
hint: "Exemple: domain='twitter', method='search'"
|
|
});
|
|
}
|
|
|
|
console.log(`📡 Requête reçue: ${domain}.${method}()`);
|
|
|
|
let client = null;
|
|
|
|
try {
|
|
// Initialisation du client Emusks
|
|
client = new Emusks();
|
|
|
|
// Connexion avec les paramètres fournis
|
|
await client.login({
|
|
auth_token,
|
|
proxy,
|
|
client: "web"
|
|
});
|
|
|
|
// Vérification que la méthode existe
|
|
if (!client[domain] || typeof client[domain][method] !== 'function') {
|
|
return res.status(400).json({
|
|
error: `La méthode client.${domain}.${method}() n'existe pas.`,
|
|
available_domains: Object.keys(client).filter(k => typeof client[k] === 'object')
|
|
});
|
|
}
|
|
|
|
// Exécution de la méthode
|
|
const startTime = Date.now();
|
|
const result = await client[domain][method](...(args || []));
|
|
const duration = Date.now() - startTime;
|
|
|
|
console.log(`✅ Succès: ${domain}.${method}() en ${duration}ms`);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
meta: {
|
|
duration_ms: duration,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
const duration = Date.now() - (startTime || Date.now());
|
|
|
|
console.error(`❌ [ERREUR] Action: ${domain}.${method}`);
|
|
console.error(` Message: ${error.message}`);
|
|
console.error(` Duration: ${duration}ms`);
|
|
|
|
if (NODE_ENV === 'development') {
|
|
console.error(error.stack);
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || "Erreur interne du serveur",
|
|
details: error.response?.data || error.response?.body || null,
|
|
meta: {
|
|
duration_ms: duration,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
});
|
|
} finally {
|
|
// Nettoyage si nécessaire
|
|
if (client && typeof client.logout === 'function') {
|
|
// optionnel: await client.logout();
|
|
}
|
|
}
|
|
});
|
|
|
|
// --- GESTION DES ERREURS 404 ---
|
|
app.use((req, res) => {
|
|
res.status(404).json({
|
|
error: 'Endpoint non trouvé',
|
|
path: req.path,
|
|
method: req.method,
|
|
available_endpoints: ['GET /', 'GET /health', 'POST /api/twitter/call']
|
|
});
|
|
});
|
|
|
|
// --- GESTION DES ERREURS GLOBALES ---
|
|
app.use((err, req, res, next) => {
|
|
console.error('🔥 Erreur globale:', err.message);
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur interne du serveur',
|
|
message: NODE_ENV === 'development' ? err.message : undefined
|
|
});
|
|
});
|
|
|
|
// --- DÉMARRAGE DU SERVEUR ---
|
|
const server = app.listen(PORT, () => {
|
|
console.log('');
|
|
console.log('╔════════════════════════════════════════════╗');
|
|
console.log('║ 🐦 Twitter Worker API Démarrée ║');
|
|
console.log('╠════════════════════════════════════════════╣');
|
|
console.log(`║ 🌍 URL: http://localhost:${PORT} ║`);
|
|
console.log(`║ 🔐 Auth: Bearer ${API_SECRET.substring(0, 8)}... ║`);
|
|
console.log(`║ 🚀 Env: ${NODE_ENV.padEnd(28)}║`);
|
|
console.log('╚════════════════════════════════════════════╝');
|
|
console.log('');
|
|
});
|
|
|
|
// --- GESTION DES SIGNAUX POUR ARRÊT PROPRE ---
|
|
process.on('SIGTERM', () => {
|
|
console.log('📶 Signal SIGTERM reçu, fermeture en cours...');
|
|
server.close(() => {
|
|
console.log('✅ Serveur fermé proprement');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
console.log('📶 Signal SIGINT reçu (Ctrl+C), fermeture en cours...');
|
|
server.close(() => {
|
|
console.log('✅ Serveur fermé proprement');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
// Gestion des erreurs non capturées
|
|
process.on('uncaughtException', (err) => {
|
|
console.error('💥 Exception non capturée:', err);
|
|
process.exit(1);
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
console.error('💥 Rejection non gérée:', reason);
|
|
process.exit(1);
|
|
}); |