mirror of
https://github.com/Jieyab89/OSINT-Cheat-sheet.git
synced 2026-07-28 22:50:54 -07:00
159 lines
5.2 KiB
HTML
159 lines
5.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>CNN News Scrapper</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<style>
|
|
.card-title { word-break: break-word; }
|
|
#search-input::placeholder { color: #adb5bd; }
|
|
.stat-badge { font-size: 0.85rem; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-dark text-light">
|
|
|
|
<div class="container mt-5">
|
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
|
<h2 class="mb-0">CNN News Scrapper</h2>
|
|
<span id="article-count" class="badge bg-secondary stat-badge"></span>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<input type="text" id="search-input" class="form-control bg-secondary text-light border-0"
|
|
placeholder="Search the article">
|
|
</div>
|
|
|
|
<div id="loading" class="text-center text-secondary py-5">
|
|
<div class="spinner-border text-light" role="status"></div>
|
|
<p class="mt-2">Loading..............</p>
|
|
</div>
|
|
|
|
<div id="news-container" class="row"></div>
|
|
<div id="empty-state" class="alert alert-secondary d-none">Article not found :(</div>
|
|
</div>
|
|
|
|
<script>
|
|
let allArticles = [];
|
|
|
|
function escapeHtml(str) {
|
|
if (str === null || str === undefined) return "";
|
|
return String(str)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
function normalizePath(path) {
|
|
if (!path) return "";
|
|
return path.replace(/\\/g, "/");
|
|
}
|
|
|
|
function renderArticles(articles) {
|
|
const container = document.getElementById('news-container');
|
|
const emptyState = document.getElementById('empty-state');
|
|
container.innerHTML = "";
|
|
|
|
if (articles.length === 0) {
|
|
emptyState.classList.remove('d-none');
|
|
return;
|
|
}
|
|
emptyState.classList.add('d-none');
|
|
|
|
articles.forEach(article => {
|
|
const title = escapeHtml(article.title || "(No title article)");
|
|
const date = escapeHtml(article.date || "");
|
|
const rawContent = article.content || "";
|
|
const preview = escapeHtml(rawContent.substring(0, 200)) + (rawContent.length > 200 ? "..." : "");
|
|
const archivePath = normalizePath(article.saved_path) + "/index.html";
|
|
const archiveHref = encodeURI(archivePath);
|
|
const originalHref = encodeURI(article.url || "#");
|
|
|
|
const col = document.createElement('div');
|
|
col.className = "col-md-6 mb-4";
|
|
|
|
col.innerHTML = `
|
|
<div class="card bg-secondary text-light h-100">
|
|
<div class="card-body d-flex flex-column">
|
|
<h5 class="card-title">${title}</h5>
|
|
<p class="text-warning">${date || "Date not available"}</p>
|
|
<p class="card-text flex-grow-1">${preview || "There is no content.........."}</p>
|
|
|
|
<div class="d-flex gap-2 mt-3">
|
|
<a href="${archiveHref}" target="_blank" rel="noopener" class="btn btn-success btn-sm">
|
|
Open the Archive
|
|
</a>
|
|
<a href="${originalHref}" target="_blank" rel="noopener" class="btn btn-primary btn-sm">
|
|
Original Source
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(col);
|
|
});
|
|
}
|
|
|
|
function updateCount(shown, total) {
|
|
const badge = document.getElementById('article-count');
|
|
badge.textContent = shown === total
|
|
? `${total} article`
|
|
: `${shown} from ${total} article`;
|
|
}
|
|
|
|
function filterArticles(query) {
|
|
const q = query.trim().toLowerCase();
|
|
if (!q) {
|
|
renderArticles(allArticles);
|
|
updateCount(allArticles.length, allArticles.length);
|
|
return;
|
|
}
|
|
const filtered = allArticles.filter(a =>
|
|
(a.title || "").toLowerCase().includes(q) ||
|
|
(a.content || "").toLowerCase().includes(q)
|
|
);
|
|
renderArticles(filtered);
|
|
updateCount(filtered.length, allArticles.length);
|
|
}
|
|
|
|
async function loadData() {
|
|
const loading = document.getElementById('loading');
|
|
try {
|
|
const res = await fetch('./cnn_results.json');
|
|
if (!res.ok) {
|
|
throw new Error(`HTTP ${res.status}`);
|
|
}
|
|
const data = await res.json();
|
|
|
|
allArticles = Array.isArray(data) ? data : [];
|
|
allArticles = [...allArticles].reverse();
|
|
|
|
loading.classList.add('d-none');
|
|
renderArticles(allArticles);
|
|
updateCount(allArticles.length, allArticles.length);
|
|
|
|
document.getElementById('search-input').addEventListener('input', (e) => {
|
|
filterArticles(e.target.value);
|
|
});
|
|
|
|
} catch (err) {
|
|
loading.classList.add('d-none');
|
|
document.getElementById('news-container').innerHTML = `
|
|
<div class="alert alert-danger">
|
|
Failed to load cnn_results.json. Makesure the local server run
|
|
(example: <code>python -m http.server</code>).
|
|
</div>
|
|
`;
|
|
console.error(err);
|
|
}
|
|
}
|
|
|
|
loadData();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|