const API_BASE = 'https://api.mcstatus.io/v2/status/java/';
const REFRESH_MS = 60000; // 60s
async function loadServerList() {
try {
const res = await fetch('./server-list.json', { cache: 'no-store' });
if (!res.ok) throw new Error('Could not load server-list.json');
const data = await res.json();
// Only active servers under "minecraft" key
return (data.minecraft || []).filter(s => s.active);
} catch (err) {
console.error(err);
document.getElementById('servers').innerHTML =
'
Failed to load server-list.json
';
return [];
}
}
function esc(s) {
return s ? String(s).replace(//g, '>') : "";
}
async function fetchStatus(host, port) {
const address = `${host}:${port}`;
const url = `${API_BASE}${encodeURIComponent(address)}?query=true&timeout=5`;
try {
const resp = await fetch(url, { cache: 'no-store' });
if (!resp.ok) return { online: false, rawError: resp.status };
return await resp.json();
} catch (err) {
return { online: false, error: err.message };
}
}
function updateCard(server, card, data) {
const idSafe = server.host.replace(/[:.\s]/g, '_');
const statusEl = card.querySelector(`#status-${idSafe}`);
const motdEl = card.querySelector(`#motd-${idSafe}`);
const modsListEl = card.querySelector(`#modslist-${idSafe}`);
const metaEl = card.querySelector(`#meta-${idSafe}`);
const iconEl = card.querySelector(`#icon-${idSafe}`);
if (data && data.online) {
const players = (data.players && data.players.online != null) ?
`${data.players.online}/${data.players.max ?? '?'}` : 'N/A';
const modsText = server.mods ? 'enabled' : 'disabled';
const whitelistText = server.whitelist ? 'enabled' : 'disabled';
let motd = Array.isArray(data.motd?.clean)
? data.motd.clean.join(' ')
: (data.motd?.clean || data.motd?.stripped || data.motd?.raw || '(no motd)');
const ipLine = server.host;
const iconUrl = `https://api.mcstatus.io/v2/icon/${encodeURIComponent(server.host + ':' + server.port)}?timeout=2`;
statusEl.innerHTML = `online | players ${esc(players)} | whitelist ${esc(whitelistText)} | mods ${esc(modsText)}`;
motdEl.innerHTML = `MOTD: ${motd}`;
if (server.mods && server.modlist) {
modsListEl.innerHTML = `
`;
} else {
modsListEl.innerHTML = '';
}
metaEl.innerHTML = `server ip: ${esc(ipLine)}`;
iconEl.src = iconUrl;
iconEl.style.visibility = '';
iconEl.onerror = () => iconEl.style.visibility = 'hidden';
} else {
statusEl.innerHTML = `offline` +
(data?.rawError || data?.error ? ` ${esc(data.rawError || data.error)}
` : '');
motdEl.innerHTML = `—`;
modsListEl.innerHTML = '';
metaEl.innerHTML = `server ip: ${esc(server.host)}`;
iconEl.style.visibility = 'hidden';
}
}
async function updateSingle(server, card) {
const idSafe = server.host.replace(/[:.\s]/g, '_');
card.querySelector(`#status-${idSafe}`).innerHTML = 'Loading…';
const data = await fetchStatus(server.host, server.port);
updateCard(server, card, data);
}
async function init() {
const list = await loadServerList();
const container = document.getElementById('servers');
container.innerHTML = '';
const cards = {};
list.forEach(s => {
const idSafe = s.host.replace(/[:.\s]/g, '_');
const card = document.createElement('div');
card.className = 'server';
card.innerHTML = `
`;
container.appendChild(card);
cards[s.host] = { server: s, card };
});
for (const { server, card } of Object.values(cards)) {
updateSingle(server, card);
}
setInterval(() => {
for (const { server, card } of Object.values(cards)) {
updateSingle(server, card);
}
}, REFRESH_MS);
}
init();