// server.js const express = require('express'); const axios = require('axios'); const path = require('path'); const app = express(); const port = process.env.PORT || 3000; // Servir les fichiers statiques app.use(express.static('public')); // Route pour vérifier que le serveur fonctionne app.get('/api/health', (req, res) => { res.json({ status: 'ok', time: new Date().toISOString() }); }); // Route principale pour extraire les données d'une campagne Ulule app.get('/api/ulule/:slug', async (req, res) => { try { const { slug } = req.params; console.log(`Récupération des données pour la campagne: ${slug}`); // Utiliser directement l'API publique d'Ulule const apiUrl = `https://api.ulule.com/v1/projects/${slug}`; console.log(`Tentative d'accès à l'API: ${apiUrl}`); const response = await axios.get(apiUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'application/json', 'Cache-Control': 'no-cache' }, timeout: 10000 }); // Vérifier si la réponse est valide if (response.status === 200 && response.data) { console.log("Données récupérées avec succès depuis l'API d'Ulule"); const projectData = response.data; // Formater les données pour notre application const formattedData = { name: projectData.name ? (projectData.name.fr || projectData.name.en || Object.values(projectData.name)[0]) : slug, amount_raised: projectData.amount_raised || 0, goal: projectData.goal || 0, currency: projectData.currency || "EUR", supporters_count: projectData.supporters_count || 0, days_left: 0, percent: projectData.percent || 0, success: true, source: 'api' }; // Calculer les jours restants if (projectData.date_end) { const endDate = new Date(projectData.date_end); const now = new Date(); const diffTime = endDate - now; formattedData.days_left = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); } return res.json(formattedData); } else { throw new Error(`Réponse invalide de l'API: ${response.status}`); } } catch (error) { console.error('Erreur lors de la récupération des données:', error.message); // En cas d'erreur, renvoyer des données de démonstration res.json({ name: req.params.slug, amount_raised: 5000, goal: 10000, currency: "EUR", supporters_count: 42, days_left: 15, percent: 50, success: false, source: 'error', error: error.message, message: "Données de démonstration en raison d'une erreur" }); } }); // Route pour récupérer les détails des récompenses d'une campagne app.get('/api/ulule/:slug/rewards', async (req, res) => { try { const { slug } = req.params; console.log(`Récupération des récompenses pour la campagne: ${slug}`); // Utiliser l'API Ulule const apiUrl = `https://api.ulule.com/v1/projects/${slug}`; const response = await axios.get(apiUrl); if (response.status === 200 && response.data && response.data.rewards) { console.log(`${response.data.rewards.length} récompenses récupérées`); // Simplifier les données des récompenses const rewards = response.data.rewards.map(reward => ({ id: reward.id, title: reward.title ? (reward.title.fr || reward.title.en || Object.values(reward.title)[0]) : '', price: reward.price, description: reward.description ? (reward.description.fr || reward.description.en || Object.values(reward.description)[0]) : '', stock: reward.stock, stock_available: reward.stock_available, orders_count: reward.orders_count, date_delivery: reward.date_delivery })); return res.json({ rewards }); } else { throw new Error(`Aucune récompense trouvée pour la campagne ${slug}`); } } catch (error) { console.error('Erreur lors de la récupération des récompenses:', error.message); return res.json({ rewards: [] }); } }); // Route pour récupérer les contributeurs d'une campagne app.get('/api/ulule/:slug/contributors', async (req, res) => { try { const { slug } = req.params; console.log(`Récupération des contributeurs pour la campagne: ${slug}`); // Pour les contributeurs, nous devons encore scraper la page car l'API publique ne fournit pas cette information const htmlUrl = `https://fr.ulule.com/${slug}/supporters/`; const response = await axios.get(htmlUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3' }, timeout: 10000 }); if (response.status === 200) { console.log(`Page des contributeurs récupérée, longueur: ${response.data.length}`); // Puisque le scraping est complexe et dépend de la structure HTML, // nous allons simplement renvoyer des données de démonstration pour cet exemple const contributors = [ { name: "Contributeur 1", reward: "Pack Tour", amount: 40 }, { name: "Contributeur 2", reward: "Pack Cavalier", amount: 26 }, { name: "Contributeur 3", reward: "Pack Roi", amount: 75 }, { name: "Contributeur 4", reward: "Pack Tour", amount: 40 }, { name: "Contributeur 5", reward: "Pack Pion", amount: 6 } ]; return res.json({ contributors }); } else { throw new Error(`Erreur lors de la récupération de la page des contributeurs: ${response.status}`); } } catch (error) { console.error('Erreur lors de la récupération des contributeurs:', error.message); return res.json({ contributors: [] }); } }); // Page d'accueil app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // Démarrer le serveur app.listen(port, '0.0.0.0', () => { console.log(`Serveur démarré sur http://0.0.0.0:${port}`); });