121 lines
4.2 KiB
JavaScript
121 lines
4.2 KiB
JavaScript
const http = require("http");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const PORT = process.env.PORT || 3000;
|
|
const CACHE_FILE = path.join(__dirname, ".league-cache.json");
|
|
const CACHE_TTL = 15 * 60 * 1000;
|
|
|
|
// football-data.org: лиги и бесплатный ключ из .env (https://www.football-data.org/client/register)
|
|
const LEAGUES = {
|
|
premier: "PL",
|
|
laliga: "PD",
|
|
bundesliga: "BL1",
|
|
seriea: "SA",
|
|
};
|
|
const FDB_API_KEY = process.env.FOOTBALL_DATA_KEY || "";
|
|
|
|
function readCache() {
|
|
try { return JSON.parse(fs.readFileSync(CACHE_FILE, "utf8")); } catch (_) { return null; }
|
|
}
|
|
function writeCache(d) {
|
|
try { fs.writeFileSync(CACHE_FILE, JSON.stringify(d)); } catch (_) {}
|
|
}
|
|
|
|
|
|
|
|
async function getLeagueTable(key) {
|
|
if (!FDB_API_KEY) return null;
|
|
const url = "https://api.football-data.org/v4/competitions/" + LEAGUES[key] + "/standings";
|
|
const res = await fetch(url, { headers: { "X-Auth-Token": FDB_API_KEY } });
|
|
if (!res.ok) throw new Error("football-data status " + res.status);
|
|
const j = await res.json();
|
|
const standing = (j.standings || []).find((s) => s.type === "TOTAL") || (j.standings || [])[0];
|
|
if (!standing) throw new Error("нет таблицы в ответе");
|
|
const rows = standing.table.map((t) => ({
|
|
name: t.team.name,
|
|
played: t.playedGames,
|
|
win: t.win,
|
|
draw: t.draw,
|
|
lost: t.lost,
|
|
gf: t.goalsFor,
|
|
ga: t.goalsAgainst,
|
|
}));
|
|
const name = j.competition && j.competition.name ? j.competition.name : "лига";
|
|
return { name, rows };
|
|
}
|
|
|
|
let refreshing = null;
|
|
|
|
async function refresh() {
|
|
if (refreshing) return refreshing;
|
|
refreshing = (async () => {
|
|
const prev = readCache();
|
|
const out = { ts: Date.now(), source: "TheSportsDB", data: {} };
|
|
for (const key of Object.keys(LEAGUE_IDS)) {
|
|
try {
|
|
const t = await getLeagueTable(LEAGUE_IDS[key]);
|
|
if (t) {
|
|
out.data[key] = { key, name: t.name, rows: t.rows };
|
|
console.log("[standings] " + key + ": " + t.rows.length + " команд");
|
|
} else {
|
|
throw new Error("исчерпаны источники");
|
|
}
|
|
} catch (e) {
|
|
out.data[key] = (prev && prev.data && prev.data[key]) || { error: "данные временно недоступны" };
|
|
console.log("[standings] " + key + ": ошибка " + String(e && e.message || e).slice(0, 80));
|
|
}
|
|
}
|
|
writeCache(out);
|
|
return out;
|
|
})();
|
|
try {
|
|
return await refreshing;
|
|
} finally {
|
|
refreshing = null;
|
|
}
|
|
}
|
|
|
|
async function getStandings() {
|
|
const cache = readCache();
|
|
if (cache && Date.now() - cache.ts < CACHE_TTL) return cache;
|
|
return refresh();
|
|
}
|
|
|
|
http.createServer(async (req, res) => {
|
|
const url = req.url.split("?")[0];
|
|
|
|
if (url === "/api/standings") {
|
|
try {
|
|
const j = await getStandings();
|
|
if (!j || !j.data) { res.writeHead(503, { "Content-Type": "application/json; charset=utf-8" }); return res.end(JSON.stringify({ ok: false })); }
|
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
return res.end(JSON.stringify({ ok: true, updatedAt: j.ts, source: j.source, data: j.data }));
|
|
} catch (e) {
|
|
const cache = readCache();
|
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
return res.end(JSON.stringify(cache && cache.data ? cache : { ok: false }));
|
|
}
|
|
}
|
|
|
|
let file = url === "/" ? "/index.html" : decodeURIComponent(url);
|
|
const full = path.normalize(path.join(__dirname, file));
|
|
if (!full.startsWith(__dirname)) { res.writeHead(403); return res.end(); }
|
|
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) { res.writeHead(404); return res.end("Not found"); }
|
|
const ext = path.extname(full).toLowerCase();
|
|
const type = {
|
|
".html": "text/html",
|
|
".css": "text/css",
|
|
".js": "application/javascript",
|
|
".svg": "image/svg+xml",
|
|
".png": "image/png",
|
|
".ico": "image/x-icon",
|
|
}[ext] || "application/octet-stream";
|
|
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
|
|
res.end(fs.readFileSync(full));
|
|
}).listen(PORT, () => {
|
|
console.log("Сервер на порту " + PORT);
|
|
setTimeout(() => { refresh().catch(() => {}); }, 1500);
|
|
setInterval(() => { refresh().catch(() => {}); }, CACHE_TTL);
|
|
});
|