This commit is contained in:
hugo
2026-03-15 15:46:07 -04:00
commit f778221f3c
41 changed files with 6362 additions and 0 deletions

255
src/app.js Normal file
View File

@@ -0,0 +1,255 @@
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);
});