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;