diff --git a/src/api/hooks/groups/useGroups.test.ts b/src/api/hooks/groups/useGroups.test.ts
index 950c4eb81..65a699969 100644
--- a/src/api/hooks/groups/useGroups.test.ts
+++ b/src/api/hooks/groups/useGroups.test.ts
@@ -1,13 +1,13 @@
import { __stub } from 'soapbox/api';
import { buildGroup } from 'soapbox/jest/factory';
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import { useGroups } from './useGroups';
const group = buildGroup({ id: '1', display_name: 'soapbox' });
const store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
}),
};
diff --git a/src/api/hooks/groups/usePendingGroups.test.ts b/src/api/hooks/groups/usePendingGroups.test.ts
index f2f76178c..33190ae0b 100644
--- a/src/api/hooks/groups/usePendingGroups.test.ts
+++ b/src/api/hooks/groups/usePendingGroups.test.ts
@@ -2,14 +2,14 @@ import { __stub } from 'soapbox/api';
import { Entities } from 'soapbox/entity-store/entities';
import { buildAccount, buildGroup } from 'soapbox/jest/factory';
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import { usePendingGroups } from './usePendingGroups';
const id = '1';
const group = buildGroup({ id, display_name: 'soapbox' });
const store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
}),
me: '1',
diff --git a/src/features/auth-login/components/login-form.test.tsx b/src/features/auth-login/components/login-form.test.tsx
index cb14335c2..2cecaa059 100644
--- a/src/features/auth-login/components/login-form.test.tsx
+++ b/src/features/auth-login/components/login-form.test.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { fireEvent, render, screen } from 'soapbox/jest/test-helpers';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import LoginForm from './login-form';
@@ -9,7 +9,7 @@ describe('', () => {
it('renders for Pleroma', () => {
const mockFn = vi.fn();
const store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '2.7.2 (compatible; Pleroma 2.3.0)',
}),
};
@@ -22,7 +22,7 @@ describe('', () => {
it('renders for Mastodon', () => {
const mockFn = vi.fn();
const store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '3.0.0',
}),
};
diff --git a/src/features/auth-login/components/login-page.test.tsx b/src/features/auth-login/components/login-page.test.tsx
index 58538bebf..fc308ff4f 100644
--- a/src/features/auth-login/components/login-page.test.tsx
+++ b/src/features/auth-login/components/login-page.test.tsx
@@ -1,14 +1,14 @@
import React from 'react';
import { render, screen } from 'soapbox/jest/test-helpers';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import LoginPage from './login-page';
describe('', () => {
it('renders correctly on load', () => {
const store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '2.7.2 (compatible; Pleroma 2.3.0)',
}),
};
diff --git a/src/features/groups/components/discover/search/search.test.tsx b/src/features/groups/components/discover/search/search.test.tsx
index f7dbafa3d..5c25cc5a3 100644
--- a/src/features/groups/components/discover/search/search.test.tsx
+++ b/src/features/groups/components/discover/search/search.test.tsx
@@ -3,12 +3,12 @@ import React from 'react';
import { __stub } from 'soapbox/api';
import { buildGroup } from 'soapbox/jest/factory';
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import Search from './search';
const store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
}),
};
diff --git a/src/features/groups/discover.test.tsx b/src/features/groups/discover.test.tsx
index 1f14f1022..880b17c1c 100644
--- a/src/features/groups/discover.test.tsx
+++ b/src/features/groups/discover.test.tsx
@@ -3,7 +3,7 @@ import React from 'react';
import { buildAccount } from 'soapbox/jest/factory';
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import Discover from './discover';
@@ -32,7 +32,7 @@ const store: any = {
},
}),
},
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
software: 'TRUTHSOCIAL',
}),
diff --git a/src/hooks/useGroupsPath.test.ts b/src/hooks/useGroupsPath.test.ts
index a7ba8979b..72af53731 100644
--- a/src/hooks/useGroupsPath.test.ts
+++ b/src/hooks/useGroupsPath.test.ts
@@ -1,14 +1,14 @@
import { __stub } from 'soapbox/api';
import { buildAccount, buildGroup, buildGroupRelationship } from 'soapbox/jest/factory';
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import { useGroupsPath } from './useGroupsPath';
describe('useGroupsPath()', () => {
test('without the groupsDiscovery feature', () => {
const store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '2.7.2 (compatible; Pleroma 2.3.0)',
}),
};
@@ -24,7 +24,7 @@ describe('useGroupsPath()', () => {
beforeEach(() => {
const userId = '1';
store = {
- instance: normalizeInstance({
+ instance: instanceSchema.parse({
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
}),
me: userId,
diff --git a/src/jest/mock-stores.tsx b/src/jest/mock-stores.tsx
index 0dc4bae79..c3ba64aac 100644
--- a/src/jest/mock-stores.tsx
+++ b/src/jest/mock-stores.tsx
@@ -1,13 +1,13 @@
import alexJson from 'soapbox/__fixtures__/pleroma-account.json';
-import { normalizeInstance } from 'soapbox/normalizers';
+import { instanceSchema } from 'soapbox/schemas';
import { buildAccount } from './factory';
/** Store with registrations open. */
-const storeOpen = { instance: normalizeInstance({ registrations: true }) };
+const storeOpen = { instance: instanceSchema.parse({ registrations: true }) };
/** Store with registrations closed. */
-const storeClosed = { instance: normalizeInstance({ registrations: false }) };
+const storeClosed = { instance: instanceSchema.parse({ registrations: false }) };
/** Store with a logged-in user. */
const storeLoggedIn = {
diff --git a/src/normalizers/index.ts b/src/normalizers/index.ts
index 12bb77d0c..03fb83021 100644
--- a/src/normalizers/index.ts
+++ b/src/normalizers/index.ts
@@ -13,7 +13,6 @@ export { FilterStatusRecord, normalizeFilterStatus } from './filter-status';
export { normalizeGroup } from './group';
export { GroupRelationshipRecord, normalizeGroupRelationship } from './group-relationship';
export { HistoryRecord, normalizeHistory } from './history';
-export { InstanceRecord, normalizeInstance } from './instance';
export { ListRecord, normalizeList } from './list';
export { LocationRecord, normalizeLocation } from './location';
export { MentionRecord, normalizeMention } from './mention';
diff --git a/src/normalizers/instance.test.ts b/src/normalizers/instance.test.ts
deleted file mode 100644
index 46793dfaf..000000000
--- a/src/normalizers/instance.test.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-import { Map as ImmutableMap, fromJS } from 'immutable';
-
-import { normalizeInstance } from './instance';
-
-describe('normalizeInstance()', () => {
- it('normalizes an empty Map', () => {
- const expected = {
- approval_required: false,
- contact_account: {},
- configuration: {
- media_attachments: {},
- chats: {
- max_characters: 5000,
- max_media_attachments: 1,
- },
- polls: {
- max_options: 4,
- max_characters_per_option: 25,
- min_expiration: 300,
- max_expiration: 2629746,
- },
- statuses: {
- max_characters: 500,
- max_media_attachments: 4,
- },
- groups: {
- max_characters_name: 50,
- max_characters_description: 160,
- },
- },
- description: '',
- description_limit: 1500,
- email: '',
- feature_quote: false,
- fedibird_capabilities: [],
- invites_enabled: false,
- languages: [],
- login_message: '',
- pleroma: {
- metadata: {
- account_activation_required: false,
- birthday_min_age: 0,
- birthday_required: false,
- features: [],
- federation: {
- enabled: true,
- exclusions: false,
- },
- },
- stats: {},
- },
- registrations: false,
- rules: [],
- short_description: '',
- stats: {
- domain_count: 0,
- status_count: 0,
- user_count: 0,
- },
- title: '',
- thumbnail: '',
- uri: '',
- urls: {},
- version: '0.0.0',
- nostr: {
- pubkey: undefined,
- relay: undefined,
- },
- };
-
- const result = normalizeInstance(ImmutableMap());
- expect(result.toJS()).toEqual(expected);
- });
-
- it('normalizes Pleroma instance with Mastodon configuration format', async () => {
- const instance = await import('soapbox/__fixtures__/pleroma-instance.json');
-
- const expected = {
- configuration: {
- statuses: {
- max_characters: 5000,
- max_media_attachments: Infinity,
- },
- polls: {
- max_options: 20,
- max_characters_per_option: 200,
- min_expiration: 0,
- max_expiration: 31536000,
- },
- },
- };
-
- const result = normalizeInstance(instance);
- expect(result.toJS()).toMatchObject(expected);
- });
-
- it('normalizes Mastodon instance with retained configuration', async () => {
- const instance = await import('soapbox/__fixtures__/mastodon-instance.json');
-
- const expected = {
- configuration: {
- statuses: {
- max_characters: 500,
- max_media_attachments: 4,
- characters_reserved_per_url: 23,
- },
- media_attachments: {
- image_size_limit: 10485760,
- image_matrix_limit: 16777216,
- video_size_limit: 41943040,
- video_frame_rate_limit: 60,
- video_matrix_limit: 2304000,
- },
- polls: {
- max_options: 4,
- max_characters_per_option: 50,
- min_expiration: 300,
- max_expiration: 2629746,
- },
- },
- };
-
- const result = normalizeInstance(instance);
- expect(result.toJS()).toMatchObject(expected);
- });
-
- it('normalizes Mastodon 3.0.0 instance with default configuration', async () => {
- const instance = await import('soapbox/__fixtures__/mastodon-3.0.0-instance.json');
-
- const expected = {
- configuration: {
- statuses: {
- max_characters: 500,
- max_media_attachments: 4,
- },
- polls: {
- max_options: 4,
- max_characters_per_option: 25,
- min_expiration: 300,
- max_expiration: 2629746,
- },
- },
- };
-
- const result = normalizeInstance(instance);
- expect(result.toJS()).toMatchObject(expected);
- });
-
- it('normalizes Fedibird instance', async () => {
- const instance = await import('soapbox/__fixtures__/fedibird-instance.json');
- const result = normalizeInstance(instance);
-
- // Sets description_limit
- expect(result.description_limit).toEqual(1500);
-
- // Preserves fedibird_capabilities
- expect(result.fedibird_capabilities).toEqual(fromJS(instance.fedibird_capabilities));
- });
-
- it('normalizes Mitra instance', async () => {
- const instance = await import('soapbox/__fixtures__/mitra-instance.json');
- const result = normalizeInstance(instance);
-
- // Adds configuration and description_limit
- expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
- expect(result.get('description_limit')).toBe(1500);
- });
-
- it('normalizes GoToSocial instance', async () => {
- const instance = await import('soapbox/__fixtures__/gotosocial-instance.json');
- const result = normalizeInstance(instance);
-
- // Normalizes max_toot_chars
- expect(result.getIn(['configuration', 'statuses', 'max_characters'])).toEqual(5000);
- expect(result.has('max_toot_chars')).toBe(false);
-
- // Adds configuration and description_limit
- expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
- expect(result.get('description_limit')).toBe(1500);
- });
-
- it('normalizes Friendica instance', async () => {
- const instance = await import('soapbox/__fixtures__/friendica-instance.json');
- const result = normalizeInstance(instance);
-
- // Normalizes max_toot_chars
- expect(result.getIn(['configuration', 'statuses', 'max_characters'])).toEqual(200000);
- expect(result.has('max_toot_chars')).toBe(false);
-
- // Adds configuration and description_limit
- expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
- expect(result.get('description_limit')).toBe(1500);
- });
-
- it('normalizes a Mastodon RC version', async () => {
- const instance = await import('soapbox/__fixtures__/mastodon-instance-rc.json');
- const result = normalizeInstance(instance);
-
- expect(result.version).toEqual('3.5.0-rc1');
- });
-
- it('normalizes Pixelfed instance', async () => {
- const instance = await import('soapbox/__fixtures__/pixelfed-instance.json');
- const result = normalizeInstance(instance);
- expect(result.title).toBe('pixelfed');
- });
-
- it('renames Akkoma to Pleroma', async () => {
- const instance = await import('soapbox/__fixtures__/akkoma-instance.json');
- const result = normalizeInstance(instance);
-
- expect(result.version).toEqual('2.7.2 (compatible; Pleroma 2.4.50+akkoma)');
- });
-});
diff --git a/src/normalizers/instance.ts b/src/normalizers/instance.ts
deleted file mode 100644
index ea3327fb6..000000000
--- a/src/normalizers/instance.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * Instance normalizer:
- * Converts API instances into our internal format.
- * @see {@link https://docs.joinmastodon.org/entities/instance/}
- */
-import {
- Map as ImmutableMap,
- List as ImmutableList,
- Record as ImmutableRecord,
- fromJS,
-} from 'immutable';
-
-import { parseVersion, PLEROMA } from 'soapbox/utils/features';
-import { mergeDefined } from 'soapbox/utils/normalizers';
-import { isNumber } from 'soapbox/utils/numbers';
-
-// Use Mastodon defaults
-// https://docs.joinmastodon.org/entities/instance/
-export const InstanceRecord = ImmutableRecord({
- approval_required: false,
- contact_account: ImmutableMap(),
- configuration: ImmutableMap({
- media_attachments: ImmutableMap(),
- chats: ImmutableMap({
- max_characters: 5000,
- max_media_attachments: 1,
- }),
- polls: ImmutableMap({
- max_options: 4,
- max_characters_per_option: 25,
- min_expiration: 300,
- max_expiration: 2629746,
- }),
- statuses: ImmutableMap({
- max_characters: 500,
- max_media_attachments: 4,
- }),
- groups: ImmutableMap({
- max_characters_name: 50,
- max_characters_description: 160,
- }),
- }),
- description: '',
- description_limit: 1500,
- email: '',
- feature_quote: false,
- fedibird_capabilities: ImmutableList(),
- invites_enabled: false,
- languages: ImmutableList(),
- login_message: '',
- pleroma: ImmutableMap({
- metadata: ImmutableMap({
- account_activation_required: false,
- birthday_min_age: 0,
- birthday_required: false,
- features: ImmutableList(),
- federation: ImmutableMap({
- enabled: true,
- exclusions: false,
- }),
- }),
- stats: ImmutableMap(),
- }),
- registrations: false,
- rules: ImmutableList(),
- short_description: '',
- stats: ImmutableMap({
- domain_count: 0,
- status_count: 0,
- user_count: 0,
- }),
- nostr: ImmutableMap({
- relay: undefined as string | undefined,
- pubkey: undefined as string | undefined,
- }),
- title: '',
- thumbnail: '',
- uri: '',
- urls: ImmutableMap(),
- version: '0.0.0',
-});
-
-// Build Mastodon configuration from Pleroma instance
-const pleromaToMastodonConfig = (instance: ImmutableMap) => {
- return ImmutableMap({
- statuses: ImmutableMap({
- max_characters: instance.get('max_toot_chars'),
- }),
- polls: ImmutableMap({
- max_options: instance.getIn(['poll_limits', 'max_options']),
- max_characters_per_option: instance.getIn(['poll_limits', 'max_option_chars']),
- min_expiration: instance.getIn(['poll_limits', 'min_expiration']),
- max_expiration: instance.getIn(['poll_limits', 'max_expiration']),
- }),
- });
-};
-
-// Get the software's default attachment limit
-const getAttachmentLimit = (software: string | null) => software === PLEROMA ? Infinity : 4;
-
-// Normalize version
-const normalizeVersion = (instance: ImmutableMap) => {
- return instance.update('version', '0.0.0', version => {
- // Handle Mastodon release candidates
- if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) {
- return version.split('rc').join('-rc');
- } else {
- return version;
- }
- });
-};
-
-/** Rename Akkoma to Pleroma+akkoma */
-const fixAkkoma = (instance: ImmutableMap) => {
- const version: string = instance.get('version', '');
-
- if (version.includes('Akkoma')) {
- return instance.set('version', '2.7.2 (compatible; Pleroma 2.4.50+akkoma)');
- } else {
- return instance;
- }
-};
-
-/** Set Takahē version to a Pleroma-like string */
-const fixTakahe = (instance: ImmutableMap) => {
- const version: string = instance.get('version', '');
-
- if (version.startsWith('takahe/')) {
- return instance.set('version', `0.0.0 (compatible; Takahe ${version.slice(7)})`);
- } else {
- return instance;
- }
-};
-
-// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format
-export const normalizeInstance = (instance: Record) => {
- return InstanceRecord(
- ImmutableMap(fromJS(instance)).withMutations((instance: ImmutableMap) => {
- const { software } = parseVersion(instance.get('version'));
- const mastodonConfig = pleromaToMastodonConfig(instance);
-
- // Merge configuration
- instance.update('configuration', ImmutableMap(), configuration => (
- configuration.mergeDeepWith(mergeDefined, mastodonConfig)
- ));
-
- // If max attachments isn't set, check the backend software
- instance.updateIn(['configuration', 'statuses', 'max_media_attachments'], value => {
- return isNumber(value) ? value : getAttachmentLimit(software);
- });
-
- // Urls can't be null, fix for Friendica
- if (instance.get('urls') === null) instance.delete('urls');
-
- // Normalize version
- normalizeVersion(instance);
- fixTakahe(instance);
- fixAkkoma(instance);
-
- // Merge defaults
- instance.mergeDeepWith(mergeDefined, InstanceRecord());
- }),
- );
-};
diff --git a/src/types/entities.ts b/src/types/entities.ts
index 20eb419af..98d36a07c 100644
--- a/src/types/entities.ts
+++ b/src/types/entities.ts
@@ -12,7 +12,6 @@ import {
FilterKeywordRecord,
FilterStatusRecord,
HistoryRecord,
- InstanceRecord,
ListRecord,
LocationRecord,
MentionRecord,
@@ -41,7 +40,6 @@ type Filter = ReturnType;
type FilterKeyword = ReturnType;
type FilterStatus = ReturnType;
type History = ReturnType;
-type Instance = ReturnType;
type List = ReturnType;
type Location = ReturnType;
type Mention = ReturnType;
@@ -77,7 +75,6 @@ export {
FilterKeyword,
FilterStatus,
History,
- Instance,
List,
Location,
Mention,