This commit is contained in:
SansGuidon 2025-05-16 19:27:51 +02:00
commit 820cc6f209
9 changed files with 1515 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

6
README.md Normal file
View File

@ -0,0 +1,6 @@
npm init -y && npm install express cheerio axios cors
node server.js
Ouvre frontend.html dans ton navigateur.
Tape une recherche, genre “minecraft”.
Laisse tourner.
Si tas une erreur, lis le message. Pleure, ou corrige.

47
aggregateur-sample.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Agrégateur Parents</title>
<style>
.film { border:1px solid #ccc; margin:10px; padding:10px; }
.film img { max-height: 180px; }
</style>
</head>
<body>
<div id="films"></div>
<script>
// Appelle ton backend qui utilise le module JS au-dessus
// Ici, on simule avec des données
const films = [
{
title: "A Minecraft Movie",
year: "2025",
imgSrc: "https://www.cinecheck.be/media/n1hojmmh/a-minecraft-movie-ov-_ps_1_jpg_sd-high_2025-warner-bros-entertainment-inc-all-rights-reserved-photo-credit-courtesy-warner-bros-pictures.jpg",
link: "https://www.cinecheck.be/films/a-minecraft-movie/",
marks: ["9 ans", "Peur"],
summary: "A Minecraft Movie, van Warner Bros. Pictures en Legendary Pictures, ...",
details: [
{ type: "angst", description: "La film contient des scènes effrayantes..." }
]
}
];
const container = document.getElementById('films');
films.forEach(f => {
const div = document.createElement('div');
div.className = 'film';
div.innerHTML = `
<h2>${f.title} (${f.year || "?"})</h2>
<img src="${f.imgSrc}" alt="cover">
<div><b>Conseil d'âge:</b> ${f.marks.join(', ')}</div>
<p>${f.summary}</p>
<ul>
${f.details.map(d => `<li><b>${d.type}:</b> ${d.description}</li>`).join('')}
</ul>
<a href="${f.link}" target="_blank">Voir sur Cinecheck</a>
`;
container.appendChild(div);
});
</script>
</body>
</html>

75
cinecheck-adapter.js Normal file
View File

@ -0,0 +1,75 @@
const axios = require('axios');
const cheerio = require('cheerio');
const CINECHECK_BASE = 'https://www.cinecheck.be';
async function searchMovies(query) {
const url = `${CINECHECK_BASE}/umbraco/surface/searchresults/search?query=${encodeURIComponent(query)}&producties=0&amount=5`;
const res = await axios.get(url, {
headers: {
'x-umb-culture': 'fr-BE',
'x-umb-key': '0a0c11a9-ece8-4dc8-8578-e5aab235d9ff',
'x-requested-with': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0',
}
});
const $ = cheerio.load(res.data);
const results = [];
$('.c-search__result').each((_, el) => {
const title = $(el).find('.c-search__title').text().trim().replace(/\s*\(.+?\)\s*$/, '');
const yearMatch = $(el).find('.c-search__title').text().match(/\((\d{4})\)/);
const year = yearMatch ? yearMatch[1] : null;
const imgSrc = $(el).find('img.c-search__image').attr('src')
? CINECHECK_BASE + $(el).find('img.c-search__image').attr('src')
: null;
const link = $(el).find('a.c-search__hiddenlink').attr('href')
? CINECHECK_BASE + $(el).find('a.c-search__hiddenlink').attr('href')
: null;
if (title && link) {
results.push({ title, year, imgSrc, link });
}
});
return results;
}
async function getMovieClassification(movieUrl) {
const res = await axios.get(movieUrl, {
headers: { 'User-Agent': 'Mozilla/5.0' }
});
const $ = cheerio.load(res.data);
const year = $('.c-movie__details .c-movie__label').first().text().trim() || null;
const genres = $('.c-movie__details .c-movie__label').eq(1).text().split(',').map(s => s.trim());
const img = $('.c-movie__cover img').attr('src')
? CINECHECK_BASE + $('.c-movie__cover img').attr('src')
: null;
const marks = [];
$('.c-header__marks .c-header__mark').each((_, el) => {
const label = $(el).find('span.vh').text().trim();
if (label) marks.push(label);
});
const details = [];
$('.c-classificatie__item').each((_, el) => {
const type = $(el).find('svg use').first().attr('xlink:href') || '';
const typeName = type.split('#')[1] || '';
const description = $(el).find('.js-classificatie-text').text().trim();
if (typeName && description) {
details.push({ type: typeName, description });
}
});
const summary = $('.c-movie__introtext p').first().text().trim();
return {
year,
genres,
img,
marks,
details,
summary
};
}
module.exports = { searchMovies, getMovieClassification };

57
frontend.html Normal file
View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Agrégateur de films pour parents</title>
<style>
body { font-family: system-ui, sans-serif; background: #252525; color: #ececec; }
.film { border:1px solid #444; margin:16px 0; padding:16px; border-radius: 6px; background: #181818; }
.film img { max-height: 180px; float:right; margin-left:16px; }
.film h2 { margin-top:0;}
.searchbox { margin:32px 0; }
input[type="text"] { font-size:1.2em; width:22em; background:#111; color:#fff; border:1px solid #444; border-radius:4px; padding:6px; }
button { font-size:1.1em; padding:6px 14px; background:#333; color:#fff; border-radius:4px; border:1px solid #555; }
.marks { font-size:0.95em; color:#b4ffb4; margin-bottom:6px; }
.details { font-size:0.93em; color: #ffbdbd;}
.year { color:#aaa;}
</style>
</head>
<body>
<h1>Cinécheck Agrégateur</h1>
<div class="searchbox">
<input type="text" id="q" placeholder="Recherche... (ex: Minecraft)" />
<button onclick="search()">Go</button>
</div>
<div id="films"></div>
<script>
async function search() {
const q = document.getElementById('q').value.trim();
if (!q) return;
document.getElementById('films').innerHTML = 'Loading...';
const res = await fetch(`http://localhost:3000/search?q=${encodeURIComponent(q)}`);
const films = await res.json();
if (!Array.isArray(films) || !films.length) {
document.getElementById('films').innerHTML = "Rien trouvé. Essaie autre chose.";
return;
}
document.getElementById('films').innerHTML = '';
films.forEach(f => {
const div = document.createElement('div');
div.className = 'film';
div.innerHTML = `
<h2>${f.title} ${f.year ? `<span class="year">(${f.year})</span>` : ''}</h2>
${f.img ? `<img src="${f.img}" alt="cover">` : ""}
<div class="marks"><b>Conseil d'âge:</b> ${f.marks ? f.marks.join(', ') : '-'}</div>
<div class="summary">${f.summary || 'Pas de résumé.'}</div>
<ul class="details">
${f.details && f.details.length ? f.details.map(d => `<li><b>${d.type}:</b> ${d.description}</li>`).join('') : ''}
</ul>
<a href="${f.link}" target="_blank" style="color:#7af;">Voir sur Cinecheck</a>
`;
document.getElementById('films').appendChild(div);
});
}
document.getElementById('q').addEventListener('keydown', e => { if (e.key === 'Enter') search(); });
</script>
</body>
</html>

47
index.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Agrégateur Parents</title>
<style>
.film { border:1px solid #ccc; margin:10px; padding:10px; }
.film img { max-height: 180px; }
</style>
</head>
<body>
<div id="films"></div>
<script>
// Appelle ton backend qui utilise le module JS au-dessus
// Ici, on simule avec des données
const films = [
{
title: "A Minecraft Movie",
year: "2025",
imgSrc: "https://www.cinecheck.be/media/n1hojmmh/a-minecraft-movie-ov-_ps_1_jpg_sd-high_2025-warner-bros-entertainment-inc-all-rights-reserved-photo-credit-courtesy-warner-bros-pictures.jpg",
link: "https://www.cinecheck.be/films/a-minecraft-movie/",
marks: ["9 ans", "Peur"],
summary: "A Minecraft Movie, van Warner Bros. Pictures en Legendary Pictures, ...",
details: [
{ type: "angst", description: "La film contient des scènes effrayantes..." }
]
}
];
const container = document.getElementById('films');
films.forEach(f => {
const div = document.createElement('div');
div.className = 'film';
div.innerHTML = `
<h2>${f.title} (${f.year || "?"})</h2>
<img src="${f.imgSrc}" alt="cover">
<div><b>Conseil d'âge:</b> ${f.marks.join(', ')}</div>
<p>${f.summary}</p>
<ul>
${f.details.map(d => `<li><b>${d.type}:</b> ${d.description}</li>`).join('')}
</ul>
<a href="${f.link}" target="_blank">Voir sur Cinecheck</a>
`;
container.appendChild(div);
});
</script>
</body>
</html>

1238
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "cine-kids",
"version": "1.0.0",
"main": "cinecheck-adapter.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.9.0",
"cheerio": "^1.0.0",
"cors": "^2.8.5",
"express": "^5.1.0"
},
"description": ""
}

25
server.js Normal file
View File

@ -0,0 +1,25 @@
const express = require('express');
const cors = require('cors');
const cinecheck = require('./cinecheck-adapter');
const app = express();
app.use(cors());
app.get('/search', async (req, res) => {
const q = req.query.q;
if (!q) return res.status(400).json({ error: "Missing query" });
try {
const results = await cinecheck.searchMovies(q);
// Enrichir chaque film avec la classification (en parallèle, le minimum pour survivre)
const enriched = await Promise.all(results.map(async m => {
const details = await cinecheck.getMovieClassification(m.link);
return { ...m, ...details };
}));
res.json(enriched);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
app.listen(3000, () => {
console.log('Backend prêt. http://localhost:3000');
});