mirror of
https://github.com/MbinOrg/mbin-website.git
synced 2025-06-29 14:48:57 +00:00
195 lines
5.4 KiB
JavaScript
195 lines
5.4 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
|
|
await fs.rm('./.output/data', { recursive: true, force: true });
|
|
await fs.mkdir('./.output/data', { recursive: true });
|
|
|
|
/** @returns {Promise<Array<import('./routes/releases').Release>>} */
|
|
const fetchReleases = async () => {
|
|
const releasesJson = await (
|
|
await fetch(
|
|
'https://api.github.com/repos/mbinOrg/mbin/releases?per_page=100',
|
|
)
|
|
).json();
|
|
|
|
/** @type {Array<import('./routes/releases').Release>} */
|
|
const output = releasesJson.map((v, i, a) => ({
|
|
version: v.tag_name.substring(1),
|
|
// A server is considered outdated if a newer version has been available for more than 30 days.
|
|
outdated:
|
|
i > 0 &&
|
|
Date.now() - Date.parse(a[i - 1].published_at) > 1000 * 60 * 60 * 24 * 30,
|
|
publishedAt: v.published_at,
|
|
githubUrl: v.html_url,
|
|
body: v.body,
|
|
}));
|
|
|
|
return output.sort((a, b) => a.publishedAt - b.publishedAt);
|
|
};
|
|
|
|
const releases = await fetchReleases();
|
|
fs.writeFile('./.output/data/releases.json', JSON.stringify(releases), 'utf8');
|
|
|
|
/**
|
|
* @returns {Promise<string>}
|
|
*/
|
|
const fetchServerList = async () => {
|
|
/** @type Set<string> */
|
|
const servers = new Set();
|
|
|
|
// Fetch Mbin servers from fediverse.observer
|
|
const fediverseObserver = await (
|
|
await fetch('https://api.fediverse.observer/', {
|
|
body: '{"query":"{nodes(softwarename:\\"mbin\\" status: \\"UP\\"){domain}}"}',
|
|
method: 'POST',
|
|
})
|
|
).json();
|
|
|
|
for (const server of fediverseObserver.data.nodes) {
|
|
servers.add(server.domain);
|
|
}
|
|
|
|
// Fetch Mbin servers from fedidb.org. The api used below is not documented and is subject to change;
|
|
// this is used due to the current publicized api not being sufficient for fetching Mbin server lists.
|
|
// Once issue #3 (https://github.com/fedidb/issues/issues/3) is complete, then we can move to api v1.
|
|
const fedidbCookies = (await fetch('https://fedidb.org')).headers
|
|
.getSetCookie()
|
|
.map((c) => c.split(';')[0]);
|
|
|
|
const fedidbHeaders = {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-XSRF-TOKEN': decodeURIComponent(
|
|
fedidbCookies
|
|
.find((c) => c.startsWith('XSRF-TOKEN='))
|
|
.substring('XSRF-TOKEN='.length),
|
|
),
|
|
Cookie: fedidbCookies.join('; '),
|
|
};
|
|
|
|
let fedidbNextCursor = '';
|
|
|
|
do {
|
|
const fedidbServers = await (
|
|
await fetch(
|
|
`https://fedidb.org/api/web/network/software/servers${
|
|
fedidbNextCursor ? `?cursor=` + fedidbNextCursor : ''
|
|
}`,
|
|
{
|
|
headers: fedidbHeaders,
|
|
body: '{"slug":"mbin"}',
|
|
method: 'POST',
|
|
},
|
|
)
|
|
).json();
|
|
|
|
for (const server of fedidbServers.data) {
|
|
servers.add(server.domain);
|
|
}
|
|
|
|
fedidbNextCursor = fedidbServers.meta.next_cursor;
|
|
} while (fedidbNextCursor);
|
|
|
|
return [...servers];
|
|
};
|
|
|
|
/**
|
|
* @param {string} domain
|
|
* @returns {Promise<import('./routes/servers').Server>}
|
|
*/
|
|
const fetchServerInfo = async (domain) => {
|
|
console.log('START:', domain);
|
|
|
|
let jsonNodeInfo;
|
|
try {
|
|
jsonNodeInfo = await (
|
|
await fetch(`https://${domain}/nodeinfo/2.1.json`)
|
|
).json();
|
|
|
|
if (jsonNodeInfo.software.name != 'mbin') {
|
|
throw new Error(`software check failed`);
|
|
}
|
|
} catch (error) {
|
|
throw new Error(`${domain}: invalid nodeinfo response (skip)`, {
|
|
cause: error,
|
|
});
|
|
}
|
|
|
|
/** @type {import('./routes/servers').Server['api']} */
|
|
let apiOutput;
|
|
try {
|
|
const jsonApiInfo = await (
|
|
await fetch(`https://${domain}/api/info`)
|
|
).json();
|
|
if (jsonApiInfo.websiteDomain != domain) {
|
|
throw new Error(`domain check failed`);
|
|
}
|
|
const jsonApiInstance = await (
|
|
await fetch(`https://${domain}/api/instance`)
|
|
).json();
|
|
const jsonApiDefederated = await (
|
|
await fetch(`https://${domain}/api/defederated`)
|
|
).json();
|
|
|
|
apiOutput = {
|
|
defaultLang: jsonApiInfo.websiteDefaultLang ?? 'en',
|
|
federationEnabled: jsonApiInfo.websiteFederationEnabled,
|
|
contactEmail: jsonApiInfo.websiteContactEmail,
|
|
pages: jsonApiInstance,
|
|
defederated: jsonApiDefederated.instances ?? [],
|
|
};
|
|
} catch (error) {
|
|
console.error(
|
|
new Error(`${domain}: invalid api response (continue)`, {
|
|
cause: error,
|
|
}),
|
|
);
|
|
}
|
|
|
|
/** @type {import('./routes/servers').Server} */
|
|
const output = {
|
|
domain: domain,
|
|
version: jsonNodeInfo.software.version,
|
|
versionOutdated: releases.find(
|
|
(v) => v.version === jsonNodeInfo.software.version,
|
|
).outdated,
|
|
name: jsonNodeInfo.metadata.nodeName,
|
|
description: jsonNodeInfo.metadata.nodeDescription,
|
|
openRegistrations: jsonNodeInfo.openRegistrations,
|
|
totalUsers: jsonNodeInfo.usage.users.total,
|
|
activeHalfyearUsers: jsonNodeInfo.usage.users.activeHalfyear,
|
|
activeMonthUsers: jsonNodeInfo.usage.users.activeMonth,
|
|
localPosts: jsonNodeInfo.usage.localPosts,
|
|
localComments: jsonNodeInfo.usage.localComments,
|
|
api: apiOutput,
|
|
};
|
|
|
|
console.log('FINISH:', domain);
|
|
|
|
return output;
|
|
};
|
|
|
|
const initServerData = async () => {
|
|
const serverDomains = await fetchServerList();
|
|
|
|
const serversJson = (
|
|
await Promise.allSettled(serverDomains.map(fetchServerInfo))
|
|
)
|
|
.filter((v) => {
|
|
const isOk = v.status == 'fulfilled';
|
|
|
|
if (!isOk) console.error(v.reason);
|
|
|
|
return isOk;
|
|
})
|
|
.map((v) => v.value);
|
|
|
|
console.log('Mbin servers found:', serversJson.length);
|
|
|
|
fs.writeFile(
|
|
'./.output/data/servers.json',
|
|
JSON.stringify(serversJson),
|
|
'utf8',
|
|
);
|
|
};
|
|
|
|
await initServerData();
|