diff --git a/src/data/features.json b/src/data/features.json
deleted file mode 100644
index 6ff4bb1e..00000000
--- a/src/data/features.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "@mastodon/edit-media-attributes": ">=4.1",
-  "@mastodon/list-exclusive": ">=4.2",
-  "@mastodon/filtered-notifications": "~4.3 || >=4.3",
-  "@mastodon/fetch-multiple-statuses": "~4.3 || >=4.3",
-  "@mastodon/trending-link-posts": "~4.3 || >=4.3",
-  "@mastodon/grouped-notifications": "~4.3 || >=4.3"
-}
diff --git a/src/utils/api.js b/src/utils/api.js
index cf6652bc..c2f742ad 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -80,7 +80,6 @@ export async function initInstance(client, instance) {
   }
   __BENCHMARK.end('fetch-instance');
   if (!info) return;
-  console.log(info);
   const {
     // v1
     uri,
@@ -89,6 +88,32 @@ export async function initInstance(client, instance) {
     domain,
     configuration: { urls: { streaming } = {} } = {},
   } = info;
+
+  let nodeInfo;
+  try {
+    if (uri || domain) {
+      let urlBase = uri || `https://${domain}`;
+      const wellKnownResponse = await fetch(`${urlBase}/.well-known/nodeinfo`);
+      if (wellKnownResponse.ok) {
+        const wellKnown = await wellKnownResponse.json();
+        if (wellKnown && Array.isArray(wellKnown.links)) {
+          const nodeInfoUrl = wellKnown.links.find(
+            (link) => typeof link.rel === 'string' &&
+            link.rel.startsWith('http://nodeinfo.diaspora.software/ns/schema/')
+          )?.href;
+          if (nodeInfoUrl && nodeInfoUrl.startsWith(urlBase)) {
+            const nodeInfoResponse = await fetch(nodeInfoUrl);
+            nodeInfo = await nodeInfoResponse.json();
+          }
+        }
+      }
+    }
+  } catch (e) {}
+  if (nodeInfo) {
+    info.nodeInfo = nodeInfo;
+  }
+  console.log(info);
+
   const instances = store.local.getJSON('instances') || {};
   if (uri || domain) {
     instances[
diff --git a/src/utils/store-utils.js b/src/utils/store-utils.js
index cc236215..c2caf9e5 100644
--- a/src/utils/store-utils.js
+++ b/src/utils/store-utils.js
@@ -163,5 +163,5 @@ export function getVapidKey() {
 
 export function isMediaFirstInstance() {
   const instance = getCurrentInstance();
-  return /pixelfed/i.test(instance?.version);
+  return instance.nodeInfo?.software?.name === 'pixelfed';
 }
diff --git a/src/utils/supports.js b/src/utils/supports.js
index 66454224..8721d925 100644
--- a/src/utils/supports.js
+++ b/src/utils/supports.js
@@ -1,51 +1,82 @@
 import { satisfies } from 'compare-versions';
 
-import features from '../data/features.json';
-
 import { getCurrentInstance } from './store-utils';
 
-// Non-semver(?) UA string detection
-const containPixelfed = /pixelfed/i;
-const notContainPixelfed = /^(?!.*pixelfed).*$/i;
-const containPleroma = /pleroma/i;
-const containAkkoma = /akkoma/i;
 const platformFeatures = {
-  '@mastodon/lists': notContainPixelfed,
-  '@mastodon/filters': notContainPixelfed,
-  '@mastodon/mentions': notContainPixelfed,
-  '@mastodon/trending-hashtags': notContainPixelfed,
-  '@mastodon/trending-links': notContainPixelfed,
-  '@mastodon/post-bookmark': notContainPixelfed,
-  '@mastodon/post-edit': notContainPixelfed,
-  '@mastodon/profile-edit': notContainPixelfed,
-  '@mastodon/profile-private-note': notContainPixelfed,
-  '@pixelfed/trending': containPixelfed,
-  '@pixelfed/home-include-reblogs': containPixelfed,
-  '@pixelfed/global-feed': containPixelfed,
-  '@pleroma/local-visibility-post': containPleroma,
-  '@akkoma/local-visibility-post': containAkkoma,
+  '@mastodon/edit-media-attributes': [['mastodon', '>=4.1']],
+  '@mastodon/list-exclusive': [
+    ['mastodon', '>=4.2'],
+    ['gotosocial', '>=0.17'],
+  ],
+  '@mastodon/filtered-notifications': [['mastodon', '>=4.3']],
+  '@mastodon/fetch-multiple-statuses': [['mastodon', '>=4.3']],
+  '@mastodon/trending-link-posts': [['mastodon', '>=4.3']],
+  '@mastodon/grouped-notifications': [['mastodon', '>=4.3']],
+  '@mastodon/lists': [['!pixelfed']],
+  '@mastodon/filters': [['!pixelfed']],
+  '@mastodon/mentions': [['!pixelfed']],
+  '@mastodon/trending-hashtags': [['!pixelfed']],
+  '@mastodon/trending-links': [['!pixelfed']],
+  '@mastodon/post-bookmark': [['!pixelfed']],
+  '@mastodon/post-edit': [['!pixelfed']],
+  '@mastodon/profile-edit': [['!pixelfed']],
+  '@mastodon/profile-private-note': [['!pixelfed']],
+  '@pixelfed/trending': [['pixelfed']],
+  '@pixelfed/home-include-reblogs': [['pixelfed']],
+  '@pixelfed/global-feed': [['pixelfed']],
+  '@pleroma/local-visibility-post': [['pleroma']],
+  '@akkoma/local-visibility-post': [['akkoma']],
 };
+
 const supportsCache = {};
 
 function supports(feature) {
+  const specs = platformFeatures[feature];
+  if (!specs) return false;
+
   try {
-    const { version, domain } = getCurrentInstance();
+    let { version, domain, nodeInfo } = getCurrentInstance();
+
     const key = `${domain}-${feature}`;
     if (supportsCache[key]) return supportsCache[key];
 
-    if (platformFeatures[feature]) {
-      return (supportsCache[key] = platformFeatures[feature].test(version));
+    let software = 'mastodon';
+    if (
+      nodeInfo && nodeInfo.software && typeof nodeInfo.software.version === 'string'
+      && typeof nodeInfo.software.name === 'string'
+    ) {
+      software = nodeInfo.software.name.toLowerCase();
+      version = nodeInfo.software.version;
     }
 
-    const range = features[feature];
-    if (!range) return false;
-    return (supportsCache[key] = satisfies(version, range, {
-      includePrerelease: true,
-      loose: true,
-    }));
+    const isSupported = specs.some((spec) => versionSatisfies(software, version, spec));
+    return (supportsCache[key] = isSupported);
   } catch (e) {
     return false;
   }
 }
 
+function versionSatisfies(software, version, [softwareSpec, versionSpec]) {
+  let softwareMatches;
+
+  // Inverted spec, like !pixelfed
+  if (softwareSpec.startsWith('!')) {
+    softwareMatches = software !== softwareSpec.slice(1);
+  } else {
+    softwareMatches = (
+      software === softwareSpec || (
+        // Hometown inherits Mastodon features
+        software === 'hometown' && softwareSpec === 'mastodon'
+      )
+    );
+  }
+
+  return softwareMatches && (
+    versionSpec == null || satisfies(version, versionSpec, {
+      includePrerelease: true,
+      loose: true,
+    })
+  );
+}
+
 export default supports;