<?php
function fetch_json($url, $accept = "application/json") {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'FediverseChecker/1.0');
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: $accept"]);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$json = json_decode($response, true);
return [$json, $http_code, $response];
}

function get_nodeinfo_url($domain) {
[$json, $code, $raw] = fetch_json("https://$domain/.well-known/nodeinfo");
if (is_array($json) && isset($json['links'])) {
foreach ($json['links'] as $link) {
if (isset($link['rel']) && strpos($link['rel'], '2.0') !== false) {
return $link['href'];
}
}
}
return null;
}

function display_result($label, $status, $extra = "") {
$emoji = $status ? "✔️" : "❌";
echo "<p>$emoji <strong>$label:</strong> $extra</p>";
}

function discover_actor_url($domain, $username = null) {
$base = "https://$domain";
// If username is not provided, try to infer from domain (before first dot)
if ($username === null) {
$username = explode('.', $domain)[0];
}
$guesses = [
"$base/$username",
"$base/users/$username",
"$base/users/admin",
"$base/@$username",
];
if ($domain === "mastodon.social") {
$guesses[] = "$base/users/gargron";
}
foreach ($guesses as $url) {
[$actor, $code, $raw] = fetch_json($url, "application/activity+json");
if ($actor && isset($actor['id'])) {
return [$url, $actor];
}
}
return [null, null];
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Fediverse Instance Checker</title>
<style>
body { font-family: sans-serif; margin: 2em; }
input[type=text] { width: 300px; padding: 5px; }
input[type=submit] { padding: 5px 10px; }
p { margin: 0.3em 0; }
</style>
</head>
<body>
<h2>🌐 Fediverse Instance Checker</h2>
<form method="get">
<label>Enter instance domain (e.g. mastodon.social):</label><br>
<input type="text" name="domain" required value="<?= htmlspecialchars($_GET['domain'] ?? '') ?>">
<input type="submit" value="Check">
</form>
<?php
if (!empty($_GET['domain'])):
$domain = trim($_GET['domain']);
$base = "https://$domain";

echo "<hr><h3>🔍 Checking $domain</h3>";

// Nodeinfo
$nodeinfo_url = get_nodeinfo_url($domain);
if ($nodeinfo_url) {
[$nodeinfo, $code] = fetch_json($nodeinfo_url);
display_result("NodeInfo (2.0)", true, "Found");
$software = $nodeinfo['software']['name'] ?? 'Unknown';
$version = $nodeinfo['software']['version'] ?? 'Unknown';
$users = $nodeinfo['usage']['users']['total'] ?? 'n/a';
$statuses = $nodeinfo['usage']['localPosts'] ?? 'n/a';
$start_time = $nodeinfo['metadata']['start_time'] ?? null;
echo "<p><strong>Software:</strong> $software</p>";
echo "<p><strong>Version:</strong> $version</p>";
echo "<p><strong>Users:</strong> $users</p>";
echo "<p><strong>Statuses:</strong> $statuses</p>";
if ($start_time) {
$since = (new DateTime())->diff(new DateTime($start_time));
echo "<p><strong>Online since:</strong> $start_time (~{$since->days} days)</p>";
}
} else {
display_result("NodeInfo", false, "Not found");
}

// Webfinger
[$webfinger, $code] = fetch_json("$base/.well-known/webfinger?resource=acct:example@$domain");
display_result("Webfinger", $code === 200, "HTTP $code");

// Instance info
[$instance, $code] = fetch_json("$base/api/v1/instance");
display_result("Instance Metadata", is_array($instance), $instance['uri'] ?? '');

// Emojis
[$emojis, $code] = fetch_json("$base/api/v1/custom_emojis");
display_result("Custom Emojis", is_array($emojis), is_array($emojis) ? count($emojis) . " found" : "");

// Peers
[$peers, $code] = fetch_json("$base/api/v1/instance/peers");
display_result("Federation Peers", is_array($peers), is_array($peers) ? count($peers) . " connected" : "");

// ActivityPub endpoints
echo "<h3>📬 ActivityPub Endpoint Discovery</h3>";
list($actor_url, $actor_json) = discover_actor_url($domain);
if (!$actor_json) {
display_result("Actor Discovery", false, "No actor found via smart guessing.");
} else {
display_result("Actor Found", true, $actor_json['preferredUsername'] ?? $actor_json['id']);
// INBOX
$inbox_url = $actor_json['inbox'] ?? null;
if ($inbox_url) {
$headers = @get_headers($inbox_url, 1);
$inbox_status = is_array($headers) && preg_match("/2\d\d|405|403/", $headers[0]);
display_result("Inbox", $inbox_status, "URL: $inbox_url");
} else {
display_result("Inbox", false);
}
// OUTBOX
$outbox_url = $actor_json['outbox'] ?? null;
if ($outbox_url) {
[$outbox_json, $outbox_code] = fetch_json($outbox_url, "application/activity+json");
$valid = $outbox_code === 200 && isset($outbox_json['type']) && stripos($outbox_json['type'], 'OrderedCollection') !== false;
display_result("Outbox", $valid, "URL: $outbox_url");
// Statuses
if (isset($outbox_json['orderedItems'][0]['id'])) {
$status_url = $outbox_json['orderedItems'][0]['id'];
[$status_json, $status_code] = fetch_json($status_url, "application/activity+json");
$valid_status = $status_code === 200 && isset($status_json['type']);
display_result("Statuses", $valid_status, "1st post: $status_url");
} else {
display_result("Statuses", false, "No recent status found");
}
} else {
display_result("Outbox", false);
display_result("Statuses", false, "No outbox to check");
}
}

// Heuristic
echo "<h3>Instance Type Guess</h3>";
if (isset($software)) {
if (stripos($software, 'akkoma') !== false) {
echo "<p>This is likely an <strong>Akkoma</strong> instance.</p>";
} elseif (stripos($software, 'mastodon') !== false) {
echo "<p>This is likely a <strong>Mastodon</strong> instance.</p>";
} elseif (stripos($software, 'pleroma') !== false) {
echo "<p>This is likely a <strong>Pleroma</strong> instance.</p>";
} else {
echo "<p>Software type: <strong>$software</strong></p>";
}
}
endif;
?>
</body>
</html>