Use NodeInfo to detect features if available

For Mastodon <=4.3 (all current stable releases of Mastodon), the
NodeInfo request will always fail due to mastodon/mastodon#23135 and
fall back to the existing behavior. For other server software, this will
allow for more accurate checking of feature availability.

Fixes #808: adds support for exclusive lists with GoToSocial 0.17+.
This commit is contained in:
Scott Feeney 2024-09-19 01:08:52 -07:00
parent 4a09740ae9
commit 839023cefc
4 changed files with 88 additions and 40 deletions

View file

@ -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"
}

View file

@ -80,7 +80,6 @@ export async function initInstance(client, instance) {
} }
__BENCHMARK.end('fetch-instance'); __BENCHMARK.end('fetch-instance');
if (!info) return; if (!info) return;
console.log(info);
const { const {
// v1 // v1
uri, uri,
@ -89,6 +88,32 @@ export async function initInstance(client, instance) {
domain, domain,
configuration: { urls: { streaming } = {} } = {}, configuration: { urls: { streaming } = {} } = {},
} = info; } = 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') || {}; const instances = store.local.getJSON('instances') || {};
if (uri || domain) { if (uri || domain) {
instances[ instances[

View file

@ -163,5 +163,5 @@ export function getVapidKey() {
export function isMediaFirstInstance() { export function isMediaFirstInstance() {
const instance = getCurrentInstance(); const instance = getCurrentInstance();
return /pixelfed/i.test(instance?.version); return instance.nodeInfo?.software?.name === 'pixelfed';
} }

View file

@ -1,51 +1,82 @@
import { satisfies } from 'compare-versions'; import { satisfies } from 'compare-versions';
import features from '../data/features.json';
import { getCurrentInstance } from './store-utils'; 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 = { const platformFeatures = {
'@mastodon/lists': notContainPixelfed, '@mastodon/edit-media-attributes': [['mastodon', '>=4.1']],
'@mastodon/filters': notContainPixelfed, '@mastodon/list-exclusive': [
'@mastodon/mentions': notContainPixelfed, ['mastodon', '>=4.2'],
'@mastodon/trending-hashtags': notContainPixelfed, ['gotosocial', '>=0.17'],
'@mastodon/trending-links': notContainPixelfed, ],
'@mastodon/post-bookmark': notContainPixelfed, '@mastodon/filtered-notifications': [['mastodon', '>=4.3']],
'@mastodon/post-edit': notContainPixelfed, '@mastodon/fetch-multiple-statuses': [['mastodon', '>=4.3']],
'@mastodon/profile-edit': notContainPixelfed, '@mastodon/trending-link-posts': [['mastodon', '>=4.3']],
'@mastodon/profile-private-note': notContainPixelfed, '@mastodon/grouped-notifications': [['mastodon', '>=4.3']],
'@pixelfed/trending': containPixelfed, '@mastodon/lists': [['!pixelfed']],
'@pixelfed/home-include-reblogs': containPixelfed, '@mastodon/filters': [['!pixelfed']],
'@pixelfed/global-feed': containPixelfed, '@mastodon/mentions': [['!pixelfed']],
'@pleroma/local-visibility-post': containPleroma, '@mastodon/trending-hashtags': [['!pixelfed']],
'@akkoma/local-visibility-post': containAkkoma, '@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 = {}; const supportsCache = {};
function supports(feature) { function supports(feature) {
const specs = platformFeatures[feature];
if (!specs) return false;
try { try {
const { version, domain } = getCurrentInstance(); let { version, domain, nodeInfo } = getCurrentInstance();
const key = `${domain}-${feature}`; const key = `${domain}-${feature}`;
if (supportsCache[key]) return supportsCache[key]; if (supportsCache[key]) return supportsCache[key];
if (platformFeatures[feature]) { let software = 'mastodon';
return (supportsCache[key] = platformFeatures[feature].test(version)); 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]; const isSupported = specs.some((spec) => versionSatisfies(software, version, spec));
if (!range) return false; return (supportsCache[key] = isSupported);
return (supportsCache[key] = satisfies(version, range, {
includePrerelease: true,
loose: true,
}));
} catch (e) { } catch (e) {
return false; 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; export default supports;