Working news

This commit is contained in:
2026-08-29 14:53:47 +02:00
parent 6032d5660c
commit c352862b46
2 changed files with 147 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
import axios from "axios";
export type NewsItem = {
title: string;
link: string;
source: string;
publishedAt: number;
};
const FEEDS: { href: string; source: string }[] = [
{
href: "/news/thn",
source: "The Hacker News",
},
{
href: "/news/cisa",
source: "CISA",
},
{
href: "/news/krebs",
source: "Krebs on Security",
},
{
href: "/news/bc",
source: "BleepingComputer",
},
];
const MAX_ITEMS = 15;
const REQUEST_TIMEOUT_MS = 10_000;
function parseDate(value: string | null | undefined): number {
if (!value) return 0;
const ms = Date.parse(value);
return Number.isNaN(ms) ? 0 : ms;
}
function textOf(el: Element | undefined | null, tag: string): string {
return el?.getElementsByTagName(tag)[0]?.textContent?.trim() ?? "";
}
function dateOf(el: Element): number {
const pubDate = textOf(el, "pubDate");
if (pubDate) return parseDate(pubDate);
const dc = el.getElementsByTagName("dc:date")[0]?.textContent?.trim();
return parseDate(dc);
}
function parseFeed(xml: string, source: string): NewsItem[] {
const doc = new DOMParser().parseFromString(xml, "text/xml");
if (doc.querySelector("parsererror")) return [];
const items: NewsItem[] = [];
doc.querySelectorAll("item").forEach((item) => {
const title = textOf(item, "title");
const link = textOf(item, "link");
if (!title || !link) return;
items.push({
title,
link,
source,
publishedAt: dateOf(item),
});
});
return items;
}
export async function getCyberNews(): Promise<NewsItem[]> {
const results = await Promise.allSettled(
FEEDS.map(({ href, source }) =>
axios
.get<string>(href, { timeout: REQUEST_TIMEOUT_MS })
.then((res) => parseFeed(res.data, source)),
),
);
const items: NewsItem[] = [];
results.forEach((result) => {
if (result.status === "fulfilled") {
items.push(...result.value);
}
});
if (items.length === 0) {
throw new Error("No cyber news available right now.");
}
const seen = new Set<string>();
const deduped = items
.sort((a, b) => b.publishedAt - a.publishedAt)
.filter((item) => {
const key = item.link;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
return deduped.slice(0, MAX_ITEMS);
}
+41
View File
@@ -2,6 +2,46 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";
import type { ProxyOptions } from "vite";
const newsProxy: Record<string, ProxyOptions> = {
"/news/thn": {
target: "https://feeds.feedburner.com",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/news\/thn/, "/TheHackersNews"),
headers: {
"User-Agent": "Mozilla/5.0",
},
},
"/news/krebs": {
target: "https://krebsonsecurity.com",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/news\/krebs/, "/feed/"),
headers: {
"User-Agent": "Mozilla/5.0",
},
},
"/news/cisa": {
target: "https://www.cisa.gov",
changeOrigin: true,
rewrite: (p) =>
p.replace(/^\/news\/cisa/, "/cybersecurity-advisories/all.xml"),
headers: {
"User-Agent": "Mozilla/5.0",
},
},
"/news/bc": {
target: "https://www.bleepingcomputer.com",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/news\/bc/, "/feed/"),
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36",
Accept:
"application/rss+xml, application/xml;q=0.9, text/xml;q=0.8, */*;q=0.7",
},
},
};
const host = process.env.TAURI_DEV_HOST;
@@ -24,6 +64,7 @@ export default defineConfig(async () => ({
port: 1420,
strictPort: true,
host: host || false,
proxy: newsProxy,
hmr: host
? {
protocol: "ws",