Add fedidb.org as server list source, add title to webpages

This commit is contained in:
John Wesley 2024-07-05 22:02:43 -04:00
parent e118d9d5cd
commit ed7d5303a0
5 changed files with 119 additions and 59 deletions

View file

@ -1,4 +1,3 @@
import { clientOnly } from '@solidjs/start';
import { ParentComponent } from 'solid-js'; import { ParentComponent } from 'solid-js';
import { SolidMarkdown } from 'solid-markdown'; import { SolidMarkdown } from 'solid-markdown';

View file

@ -1,5 +1,5 @@
// @refresh reload // @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server"; import { createHandler, StartServer } from '@solidjs/start/server';
export default createHandler(() => ( export default createHandler(() => (
<StartServer <StartServer
@ -9,6 +9,7 @@ export default createHandler(() => (
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<title>Join Mbin</title>
{assets} {assets}
</head> </head>
<body> <body>

View file

@ -3,7 +3,14 @@ import fs from 'node:fs/promises';
await fs.rm('./.output/data', { recursive: true, force: true }); await fs.rm('./.output/data', { recursive: true, force: true });
await fs.mkdir('./.output/data', { recursive: true }); await fs.mkdir('./.output/data', { recursive: true });
const initServerData = async () => { /**
* @returns {Promise<string>}
*/
const fetchServerList = async () => {
/** @type Set<string> */
const servers = new Set();
// Fetch Mbin servers from fediverse.observer
const fediverseObserver = await ( const fediverseObserver = await (
await fetch('https://api.fediverse.observer/', { await fetch('https://api.fediverse.observer/', {
body: '{"query":"{nodes(softwarename:\\"mbin\\" status: \\"UP\\"){domain}}"}', body: '{"query":"{nodes(softwarename:\\"mbin\\" status: \\"UP\\"){domain}}"}',
@ -11,65 +18,118 @@ const initServerData = async () => {
}) })
).json(); ).json();
/** @type string[] */ for (const server of fediverseObserver.data.nodes) {
const servers = fediverseObserver.data.nodes.map((v) => v.domain); servers.add(server.domain);
}
const serversJson = ( // Fetch Mbin servers from fedidb.org. The api used below is not documented and is subject to change;
await Promise.allSettled( // this is used due to the current publicized api not being sufficient for fetching Mbin server lists.
servers.map(async (serverHost) => { // Once issue #3 (https://github.com/fedidb/issues/issues/3) is complete, then we can move to api v1.
console.log('START:', serverHost); const fedidbCookies = (await fetch('https://fedidb.org')).headers
.getSetCookie()
.map((c) => c.split(';')[0]);
const jsonNodeInfo = await ( const fedidbHeaders = {
await fetch(`https://${serverHost}/nodeinfo/2.1.json`) Accept: 'application/json',
).json(); 'Content-Type': 'application/json',
'X-XSRF-TOKEN': decodeURIComponent(
fedidbCookies
.find((c) => c.startsWith('XSRF-TOKEN='))
.substring('XSRF-TOKEN='.length),
),
Cookie: fedidbCookies.join('; '),
};
if (jsonNodeInfo.software.name != 'mbin') { let fedidbNextCursor = '';
throw new Error(
`${serverHost} software does not match mbin (skipped)`,
);
}
const jsonApiInfo = await ( do {
await fetch(`https://${serverHost}/api/info`) const fedidbServers = await (
).json(); await fetch(
if (jsonApiInfo.websiteDomain != serverHost) { `https://fedidb.org/api/web/network/software/servers${
throw new Error(`${serverHost} api not setup correctly (skipped)`); fedidbNextCursor ? `?cursor=` + fedidbNextCursor : ''
} }`,
const jsonApiInstance = await ( {
await fetch(`https://${serverHost}/api/instance`) headers: fedidbHeaders,
).json(); body: '{"slug":"mbin"}',
const jsonApiDefederated = await ( method: 'POST',
await fetch(`https://${serverHost}/api/defederated`) },
).json(); )
).json();
console.log('FINISH:', serverHost); for (const server of fedidbServers.data) {
servers.add(server.domain);
}
/** @type import('./routes/servers').Server */ fedidbNextCursor = fedidbServers.meta.next_cursor;
const server = { } while (fedidbNextCursor);
domain: serverHost,
version: jsonNodeInfo.software.version,
name: jsonNodeInfo.metadata.nodeName,
description: jsonNodeInfo.metadata.nodeDescription,
openRegistrations: jsonNodeInfo.openRegistrations,
federationEnabled: jsonApiInfo.websiteFederationEnabled,
language: jsonApiInfo.websiteDefaultLang ?? 'en',
contactEmail: jsonApiInfo.websiteContactEmail,
totalUsers: jsonNodeInfo.usage.users.total,
activeHalfyearUsers: jsonNodeInfo.usage.users.activeHalfyear,
activeMonthUsers: jsonNodeInfo.usage.users.activeMonth,
localPosts: jsonNodeInfo.usage.localPosts,
localComments: jsonNodeInfo.usage.localComments,
pages: jsonApiInstance,
defederated: jsonApiDefederated.instances ?? [],
};
return server; return [...servers];
}), };
)
) /**
.filter((v) => v.status == 'fulfilled') * @param {string} domain
* @returns {Promise<import('./routes/servers').Server>}
*/
const fetchServerInfo = async (domain) => {
console.log('START:', domain);
const jsonNodeInfo = await (
await fetch(`https://${domain}/nodeinfo/2.1.json`)
).json();
if (jsonNodeInfo.software.name != 'mbin') {
throw new Error(`${domain} software does not match mbin (skipped)`);
}
const jsonApiInfo = await (await fetch(`https://${domain}/api/info`)).json();
if (jsonApiInfo.websiteDomain != domain) {
throw new Error(`${domain} api not setup correctly (skipped)`);
}
const jsonApiInstance = await (
await fetch(`https://${domain}/api/instance`)
).json();
const jsonApiDefederated = await (
await fetch(`https://${domain}/api/defederated`)
).json();
console.log('FINISH:', domain);
return {
domain: domain,
version: jsonNodeInfo.software.version,
name: jsonNodeInfo.metadata.nodeName,
description: jsonNodeInfo.metadata.nodeDescription,
openRegistrations: jsonNodeInfo.openRegistrations,
federationEnabled: jsonApiInfo.websiteFederationEnabled,
language: jsonApiInfo.websiteDefaultLang ?? 'en',
contactEmail: jsonApiInfo.websiteContactEmail,
totalUsers: jsonNodeInfo.usage.users.total,
activeHalfyearUsers: jsonNodeInfo.usage.users.activeHalfyear,
activeMonthUsers: jsonNodeInfo.usage.users.activeMonth,
localPosts: jsonNodeInfo.usage.localPosts,
localComments: jsonNodeInfo.usage.localComments,
pages: jsonApiInstance,
defederated: jsonApiDefederated.instances ?? [],
};
};
const initServerData = async () => {
const servers = await fetchServerList();
const serversJson = (await Promise.allSettled(servers.map(fetchServerInfo)))
.filter((v) => {
const isOk = v.status == 'fulfilled';
if (!isOk) {
console.error(v.reason);
}
return isOk;
})
.map((v) => v.value); .map((v) => v.value);
console.log('Successful Mbin servers found:', serversJson.length);
fs.writeFile( fs.writeFile(
'./.output/data/servers.json', './.output/data/servers.json',
JSON.stringify(serversJson), JSON.stringify(serversJson),

View file

@ -1,7 +1,7 @@
import type { ClassValue } from "clsx" import type { ClassValue } from 'clsx';
import { clsx } from "clsx" import { clsx } from 'clsx';
import { twMerge } from "tailwind-merge" import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }

View file

@ -7,7 +7,7 @@ export default function Home() {
Mbin Mbin
</h1> </h1>
<p class="my-10 text-2xl text-gray-400 block mx-auto"> <p class="my-10 text-2xl text-gray-400 block mx-auto">
a federated content aggregator, voting, discussion and microblogging A federated content aggregator, voting, discussion and microblogging
platform platform
<br /> <br />
(By the community, for the community) (By the community, for the community)