-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
116 lines (93 loc) · 3.37 KB
/
server.js
File metadata and controls
116 lines (93 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
const express = require("express");
const app = express();
const API_BASE = "https://soccer.highlightly.net";
const API_KEY = process.env.HIGHLIGHTLY_API_KEY || "YOUR_API_KEY";
const MODE = (process.env.TRACKER_MODE || "all").toLowerCase();
const WORLD_CUP_ID = 1635;
const HEADERS = { "x-rapidapi-key": API_KEY };
// Page size limits per endpoint (from API docs)
const PAGE_LIMITS = { matches: 100, highlights: 40 };
const MAX_PAGES = 50;
app.use(express.static("public"));
// --- Helpers ---
function buildUrl(endpoint, params) {
const filter = MODE === "worldcup" ? `leagueId=${WORLD_CUP_ID}` : "";
const query = [params, filter].filter(Boolean).join("&");
return `${API_BASE}/${endpoint}?${query}`;
}
async function apiFetch(url) {
const res = await fetch(url, { headers: HEADERS });
if (!res.ok) {
const text = await res.text();
let parsed;
try { parsed = JSON.parse(text); } catch { parsed = text; }
const msg = parsed?.message || parsed?.error || String(parsed);
const remaining = res.headers.get("x-ratelimit-requests-remaining");
console.error(`API ${res.status} ${url.split("?")[0]} | ${msg}` +
(remaining != null ? ` | remaining: ${remaining}` : ""));
const err = new Error(msg);
err.status = res.status;
throw err;
}
return res.json();
}
async function fetchAllPages(endpoint, params) {
const limit = PAGE_LIMITS[endpoint] || 100;
const baseUrl = buildUrl(endpoint, params);
let all = [];
for (let page = 0; page < MAX_PAGES; page++) {
const offset = page * limit;
const json = await apiFetch(`${baseUrl}&limit=${limit}&offset=${offset}`);
const items = json.data || [];
all = all.concat(items);
if (offset + limit >= (json.pagination?.totalCount || 0) || items.length === 0) break;
}
return all;
}
function sendError(res, err, fallback) {
const status = err.status || 500;
const messages = {
401: "Invalid API key. Check your HIGHLIGHTLY_API_KEY.",
403: "API quota exceeded. The free tier allows 100 requests per day.",
429: "API quota exceeded. The free tier allows 100 requests per day.",
};
res.status(status).json({
error: messages[status] || fallback,
detail: err.message,
});
}
// --- Routes ---
app.get("/api/config", (_req, res) => {
res.json({ mode: MODE });
});
app.get("/api/matches", async (req, res) => {
const date = req.query.date || new Date().toISOString().split("T")[0];
try {
res.json({ data: await fetchAllPages("matches", `date=${date}`) });
} catch (err) {
sendError(res, err, "Failed to fetch matches");
}
});
app.get("/api/matches/:id", async (req, res) => {
try {
res.json(await apiFetch(`${API_BASE}/matches/${req.params.id}`));
} catch (err) {
sendError(res, err, "Failed to fetch match details");
}
});
app.get("/api/highlights", async (req, res) => {
const date = req.query.date || new Date().toISOString().split("T")[0];
try {
res.json({ data: await fetchAllPages("highlights", `date=${date}`) });
} catch (err) {
sendError(res, err, "Failed to fetch highlights");
}
});
// --- Start ---
app.listen(3000, () => {
const modeLabel = MODE === "worldcup" ? "World Cup 2026 only" : "all leagues";
console.log(`Live Score Tracker running at http://localhost:3000 [${modeLabel}]`);
if (API_KEY === "YOUR_API_KEY") {
console.log(`\n Set your key: $env:HIGHLIGHTLY_API_KEY="your_key"; node server.js\n`);
}
});