sforkowany z mirror/soapbox
Porównaj commity
9 Commity
develop
...
instancev2
Autor | SHA1 | Data |
---|---|---|
marcin mikołajczak | f7fc865714 | |
marcin mikołajczak | 200b863e0e | |
marcin mikołajczak | 639c563107 | |
marcin mikołajczak | f11702acc7 | |
marcin mikołajczak | 9ee740d065 | |
marcin mikołajczak | c3141c4ae9 | |
marcin mikołajczak | 6fb05cbff4 | |
marcin mikołajczak | ae0a68db36 | |
marcin mikołajczak | 6ecb870c88 |
|
@ -0,0 +1,128 @@
|
|||
{
|
||||
"domain": "mastodon.social",
|
||||
"title": "Mastodon",
|
||||
"version": "4.0.2",
|
||||
"source_url": "https://github.com/mastodon/mastodon",
|
||||
"description": "The original server operated by the Mastodon gGmbH non-profit",
|
||||
"usage": { "users": { "active_month": 227900 } },
|
||||
"thumbnail": {
|
||||
"url": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png",
|
||||
"blurhash": "UeKUpFxuo~R%0nW;WCnhF6RjaJt757oJodS$",
|
||||
"versions": {
|
||||
"@1x": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png",
|
||||
"@2x": "https://files.mastodon.social/site_uploads/files/000/000/001/@2x/57c12f441d083cde.png"
|
||||
}
|
||||
},
|
||||
"languages": ["en"],
|
||||
"configuration": {
|
||||
"urls": { "streaming": "wss://mastodon.social" },
|
||||
"accounts": { "max_featured_tags": 10 },
|
||||
"statuses": {
|
||||
"max_characters": 500,
|
||||
"max_media_attachments": 4,
|
||||
"characters_reserved_per_url": 23
|
||||
},
|
||||
"media_attachments": {
|
||||
"supported_mime_types": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/heic",
|
||||
"image/heif",
|
||||
"image/webp",
|
||||
"image/avif",
|
||||
"video/webm",
|
||||
"video/mp4",
|
||||
"video/quicktime",
|
||||
"video/ogg",
|
||||
"audio/wave",
|
||||
"audio/wav",
|
||||
"audio/x-wav",
|
||||
"audio/x-pn-wave",
|
||||
"audio/vnd.wave",
|
||||
"audio/ogg",
|
||||
"audio/vorbis",
|
||||
"audio/mpeg",
|
||||
"audio/mp3",
|
||||
"audio/webm",
|
||||
"audio/flac",
|
||||
"audio/aac",
|
||||
"audio/m4a",
|
||||
"audio/x-m4a",
|
||||
"audio/mp4",
|
||||
"audio/3gpp",
|
||||
"video/x-ms-asf"
|
||||
],
|
||||
"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
|
||||
},
|
||||
"translation": { "enabled": true }
|
||||
},
|
||||
"registrations": {
|
||||
"enabled": false,
|
||||
"approval_required": false,
|
||||
"message": null
|
||||
},
|
||||
"contact": {
|
||||
"email": "staff@mastodon.social",
|
||||
"account": {
|
||||
"id": "1",
|
||||
"username": "Gargron",
|
||||
"acct": "Gargron",
|
||||
"display_name": "Eugen Rochko",
|
||||
"locked": false,
|
||||
"bot": false,
|
||||
"discoverable": true,
|
||||
"group": false,
|
||||
"created_at": "2016-03-16T00:00:00.000Z",
|
||||
"note": "\u003cp\u003eFounder, CEO and lead developer \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, Germany.\u003c/p\u003e",
|
||||
"url": "https://mastodon.social/@Gargron",
|
||||
"avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg",
|
||||
"avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg",
|
||||
"header": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg",
|
||||
"header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg",
|
||||
"followers_count": 263431,
|
||||
"following_count": 327,
|
||||
"statuses_count": 72827,
|
||||
"last_status_at": "2022-12-03",
|
||||
"noindex": false,
|
||||
"emojis": [],
|
||||
"fields": [
|
||||
{
|
||||
"name": "Patreon",
|
||||
"value": "\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
|
||||
"verified_at": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"id": "1",
|
||||
"text": "Sexually explicit or violent media must be marked as sensitive when posting"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"text": "No racism, sexism, homophobia, transphobia, xenophobia, or casteism"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"text": "No incitement of violence or promotion of violent ideologies"
|
||||
},
|
||||
{ "id": "4", "text": "No harassment, dogpiling or doxxing of other users" },
|
||||
{ "id": "5", "text": "No content illegal in Germany" },
|
||||
{
|
||||
"id": "7",
|
||||
"text": "Do not share intentionally false or misleading information"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import get from 'lodash/get';
|
||||
import { gte } from 'semver';
|
||||
|
||||
import KVStore from 'soapbox/storage/kv-store';
|
||||
import { RootState } from 'soapbox/store';
|
||||
import { getAuthUserUrl } from 'soapbox/utils/auth';
|
||||
import { parseVersion } from 'soapbox/utils/features';
|
||||
import { MASTODON, parseVersion, PLEROMA, REBASED } from 'soapbox/utils/features';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
|
@ -27,25 +28,48 @@ export const getHost = (state: RootState) => {
|
|||
export const rememberInstance = createAsyncThunk(
|
||||
'instance/remember',
|
||||
async(host: string) => {
|
||||
return await KVStore.getItemOrError(`instance:${host}`);
|
||||
const instance = await KVStore.getItemOrError(`instance:${host}`);
|
||||
|
||||
return { instance, host };
|
||||
},
|
||||
);
|
||||
|
||||
const supportsInstanceV2 = (instance: Record<string, any>): boolean => {
|
||||
const v = parseVersion(get(instance, 'version'));
|
||||
return (v.software === MASTODON && gte(v.compatVersion, '4.0.0')) ||
|
||||
(v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.5.0'));
|
||||
};
|
||||
|
||||
/** We may need to fetch nodeinfo on Pleroma < 2.1 */
|
||||
const needsNodeinfo = (instance: Record<string, any>): boolean => {
|
||||
const v = parseVersion(get(instance, 'version'));
|
||||
return v.software === 'Pleroma' && !get(instance, ['pleroma', 'metadata']);
|
||||
return v.software === PLEROMA && !get(instance, ['pleroma', 'metadata']);
|
||||
};
|
||||
|
||||
export const fetchInstance = createAsyncThunk<void, void, { state: RootState }>(
|
||||
export const fetchInstance = createAsyncThunk<{ instance: Record<string, any>, host?: string | null }, string | null | undefined, { state: RootState }>(
|
||||
'instance/fetch',
|
||||
async(_arg, { dispatch, getState, rejectWithValue }) => {
|
||||
async(host, { dispatch, getState, rejectWithValue }) => {
|
||||
try {
|
||||
const { data: instance } = await api(getState).get('/api/v1/instance');
|
||||
if (supportsInstanceV2(instance)) {
|
||||
return dispatch(fetchInstanceV2(host)) as any as { instance: Record<string, any>, host?: string | null };
|
||||
}
|
||||
if (needsNodeinfo(instance)) {
|
||||
dispatch(fetchNodeinfo());
|
||||
}
|
||||
return instance;
|
||||
return { instance, host };
|
||||
} catch (e) {
|
||||
return rejectWithValue(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchInstanceV2 = createAsyncThunk<{ instance: Record<string, any>, host?: string | null }, string | null | undefined, { state: RootState }>(
|
||||
'instance/fetch',
|
||||
async(host, { getState, rejectWithValue }) => {
|
||||
try {
|
||||
const { data: instance } = await api(getState).get('/api/v2/instance');
|
||||
return { instance, host };
|
||||
} catch (e) {
|
||||
return rejectWithValue(e);
|
||||
}
|
||||
|
@ -57,10 +81,11 @@ export const loadInstance = createAsyncThunk<void, void, { state: RootState }>(
|
|||
'instance/load',
|
||||
async(_arg, { dispatch, getState }) => {
|
||||
const host = getHost(getState());
|
||||
await Promise.all([
|
||||
dispatch(rememberInstance(host || '')),
|
||||
dispatch(fetchInstance()),
|
||||
]);
|
||||
const rememberedInstance = await dispatch(rememberInstance(host || ''));
|
||||
|
||||
if (rememberedInstance.payload && supportsInstanceV2((rememberedInstance.payload as any).instance)) {
|
||||
await dispatch(fetchInstanceV2(host));
|
||||
} else dispatch(fetchInstance(host));
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ const SoapboxMount = () => {
|
|||
<Route exact path='/about/:slug?' component={PublicLayout} />
|
||||
<Route path='/login' component={AuthLayout} />
|
||||
|
||||
{(features.accountCreation && instance.registrations) && (
|
||||
{(features.accountCreation && instance.registrations.get('enabled')) && (
|
||||
<Route exact path='/signup' component={AuthLayout} />
|
||||
)}
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ const generateConfig = (mode: RegistrationMode) => {
|
|||
};
|
||||
|
||||
const modeFromInstance = (instance: Instance): RegistrationMode => {
|
||||
if (instance.approval_required && instance.registrations) return 'approval';
|
||||
return instance.registrations ? 'open' : 'closed';
|
||||
if (instance.registrations.get('approval_required') && instance.registrations.get('enabled')) return 'approval';
|
||||
return instance.registrations.get('enabled') ? 'open' : 'closed';
|
||||
};
|
||||
|
||||
/** Allows changing the registration mode of the instance, eg "open", "closed", "approval" */
|
||||
|
|
|
@ -76,7 +76,7 @@ const Ad: React.FC<IAd> = ({ ad }) => {
|
|||
<Card className='py-6 sm:p-5' variant='rounded'>
|
||||
<Stack space={4}>
|
||||
<HStack alignItems='center' space={3}>
|
||||
<Avatar src={instance.thumbnail} size={42} />
|
||||
<Avatar src={instance.thumbnail.get('url')} size={42} />
|
||||
|
||||
<Stack grow>
|
||||
<HStack space={1}>
|
||||
|
|
|
@ -46,7 +46,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
|
|||
|
||||
const locale = settings.get('locale');
|
||||
const needsConfirmation = !!instance.pleroma.getIn(['metadata', 'account_activation_required']);
|
||||
const needsApproval = instance.approval_required;
|
||||
const needsApproval = instance.registrations.get('approval_required');
|
||||
const supportsEmailList = features.emailList;
|
||||
const supportsAccountLookup = features.accountLookup;
|
||||
const birthdayRequired = instance.pleroma.getIn(['metadata', 'birthday_required']);
|
||||
|
|
|
@ -13,7 +13,9 @@ interface IUploadCompose {
|
|||
const UploadCompose: React.FC<IUploadCompose> = ({ composeId, id }) => {
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const { description_limit: descriptionLimit } = useInstance();
|
||||
const { pleroma } = useInstance();
|
||||
|
||||
const descriptionLimit = pleroma.getIn(['metadata', 'description_limit']) as number;
|
||||
|
||||
const media = useCompose(composeId).media_attachments.find(item => item.id === id)!;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ describe('<FeedCarousel />', () => {
|
|||
features: [],
|
||||
}),
|
||||
}),
|
||||
configuration: ImmutableMap({}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -12,9 +12,11 @@ describe('<LandingPage />', () => {
|
|||
const state = rootReducer(undefined, {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: {
|
||||
instance: {
|
||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||
registrations: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(<LandingPage />, undefined, state);
|
||||
|
@ -29,9 +31,11 @@ describe('<LandingPage />', () => {
|
|||
const state = rootReducer(undefined, {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: {
|
||||
instance: {
|
||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||
registrations: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(<LandingPage />, undefined, state);
|
||||
|
@ -71,9 +75,11 @@ describe('<LandingPage />', () => {
|
|||
const state = applyActions(undefined, [{
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: {
|
||||
instance: {
|
||||
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
||||
registrations: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: PEPE_FETCH_INSTANCE_SUCCESS,
|
||||
instance: {
|
||||
|
|
|
@ -93,7 +93,7 @@ const LandingPage = () => {
|
|||
return renderProvider();
|
||||
} else if (pepeEnabled && pepeOpen) {
|
||||
return renderPepe();
|
||||
} else if (features.accountCreation && instance.registrations) {
|
||||
} else if (features.accountCreation && instance.registrations.get('enabled')) {
|
||||
return renderOpen();
|
||||
} else {
|
||||
return renderClosed();
|
||||
|
@ -113,7 +113,7 @@ const LandingPage = () => {
|
|||
|
||||
<Markup
|
||||
size='lg'
|
||||
dangerouslySetInnerHTML={{ __html: instance.short_description || instance.description }}
|
||||
dangerouslySetInnerHTML={{ __html: instance.description }}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
|
|
|
@ -359,7 +359,7 @@ const UI: React.FC<IUI> = ({ children }) => {
|
|||
|
||||
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.openId !== null);
|
||||
const accessToken = useAppSelector(state => getAccessToken(state));
|
||||
const streamingUrl = instance.urls.get('streaming_api');
|
||||
const streamingUrl = instance.configuration.getIn(['urls', 'streaming']);
|
||||
const standalone = useAppSelector(isStandalone);
|
||||
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
|
|
|
@ -13,7 +13,7 @@ export const useRegistrationStatus = () => {
|
|||
|
||||
return {
|
||||
/** Registrations are open, either through Pepe or traditional account creation. */
|
||||
isOpen: (features.accountCreation && instance.registrations) || (pepeEnabled && pepeOpen),
|
||||
isOpen: (features.accountCreation && instance.registrations.get('enabled')) || (pepeEnabled && pepeOpen),
|
||||
/** Whether Pepe is open. */
|
||||
pepeOpen,
|
||||
/** Whether Pepe is enabled. */
|
||||
|
|
|
@ -5,8 +5,6 @@ import { normalizeInstance } from '../instance';
|
|||
describe('normalizeInstance()', () => {
|
||||
it('normalizes an empty Map', () => {
|
||||
const expected = {
|
||||
approval_required: false,
|
||||
contact_account: {},
|
||||
configuration: {
|
||||
media_attachments: {},
|
||||
chats: {
|
||||
|
@ -22,13 +20,22 @@ describe('normalizeInstance()', () => {
|
|||
max_characters: 500,
|
||||
max_media_attachments: 4,
|
||||
},
|
||||
translation: {
|
||||
enabled: false,
|
||||
},
|
||||
urls: {
|
||||
streaming: '',
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
account: {},
|
||||
email: '',
|
||||
},
|
||||
description: '',
|
||||
description_limit: 1500,
|
||||
domain: '',
|
||||
email: '',
|
||||
feature_quote: false,
|
||||
fedibird_capabilities: [],
|
||||
invites_enabled: false,
|
||||
languages: [],
|
||||
login_message: '',
|
||||
pleroma: {
|
||||
|
@ -36,6 +43,7 @@ describe('normalizeInstance()', () => {
|
|||
account_activation_required: false,
|
||||
birthday_min_age: 0,
|
||||
birthday_required: false,
|
||||
description_limit: 1500,
|
||||
features: [],
|
||||
federation: {
|
||||
enabled: true,
|
||||
|
@ -44,18 +52,23 @@ describe('normalizeInstance()', () => {
|
|||
},
|
||||
stats: {},
|
||||
},
|
||||
registrations: false,
|
||||
rules: [],
|
||||
short_description: '',
|
||||
stats: {
|
||||
domain_count: 0,
|
||||
status_count: 0,
|
||||
user_count: 0,
|
||||
registrations: {
|
||||
approval_required: false,
|
||||
enabled: false,
|
||||
message: '',
|
||||
},
|
||||
rules: [],
|
||||
source_url: '',
|
||||
stats: {},
|
||||
title: '',
|
||||
thumbnail: '',
|
||||
uri: '',
|
||||
urls: {},
|
||||
thumbnail: {
|
||||
url: '',
|
||||
},
|
||||
usage: {
|
||||
users: {
|
||||
active_month: 0,
|
||||
},
|
||||
},
|
||||
version: '0.0.0',
|
||||
};
|
||||
|
||||
|
@ -142,7 +155,7 @@ describe('normalizeInstance()', () => {
|
|||
const result = normalizeInstance(instance);
|
||||
|
||||
// Sets description_limit
|
||||
expect(result.description_limit).toEqual(1500);
|
||||
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toEqual(1500);
|
||||
|
||||
// Preserves fedibird_capabilities
|
||||
expect(result.fedibird_capabilities).toEqual(fromJS(instance.fedibird_capabilities));
|
||||
|
@ -154,7 +167,7 @@ describe('normalizeInstance()', () => {
|
|||
|
||||
// Adds configuration and description_limit
|
||||
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
||||
expect(result.get('description_limit')).toBe(1500);
|
||||
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
|
||||
});
|
||||
|
||||
it('normalizes GoToSocial instance', () => {
|
||||
|
@ -167,7 +180,7 @@ describe('normalizeInstance()', () => {
|
|||
|
||||
// Adds configuration and description_limit
|
||||
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
||||
expect(result.get('description_limit')).toBe(1500);
|
||||
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
|
||||
});
|
||||
|
||||
it('normalizes Friendica instance', () => {
|
||||
|
@ -180,7 +193,7 @@ describe('normalizeInstance()', () => {
|
|||
|
||||
// Adds configuration and description_limit
|
||||
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
||||
expect(result.get('description_limit')).toBe(1500);
|
||||
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
|
||||
});
|
||||
|
||||
it('normalizes a Mastodon RC version', () => {
|
||||
|
|
|
@ -17,8 +17,10 @@ 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<string, any>(),
|
||||
contact: ImmutableMap<string, any>({
|
||||
account: ImmutableMap<string, any>(),
|
||||
email: '',
|
||||
}),
|
||||
configuration: ImmutableMap<string, any>({
|
||||
media_attachments: ImmutableMap<string, any>(),
|
||||
chats: ImmutableMap<string, number>({
|
||||
|
@ -34,15 +36,31 @@ export const InstanceRecord = ImmutableRecord({
|
|||
max_characters: 500,
|
||||
max_media_attachments: 4,
|
||||
}),
|
||||
translation: ImmutableMap<string, any>({ enabled: false }),
|
||||
urls: ImmutableMap<string, string>(),
|
||||
}),
|
||||
description: '',
|
||||
description_limit: 1500,
|
||||
domain: '',
|
||||
email: '',
|
||||
feature_quote: false,
|
||||
fedibird_capabilities: ImmutableList(),
|
||||
invites_enabled: false,
|
||||
languages: ImmutableList(),
|
||||
login_message: '',
|
||||
registrations: ImmutableMap<string, any>({
|
||||
approval_required: false,
|
||||
enabled: false,
|
||||
message: '',
|
||||
|
||||
}),
|
||||
rules: ImmutableList(),
|
||||
source_url: '',
|
||||
stats: ImmutableMap<string, number>(),
|
||||
title: '',
|
||||
thumbnail: ImmutableMap<string, any>(),
|
||||
usage: ImmutableMap<string, any>({
|
||||
users: ImmutableMap<string, number>({
|
||||
active_month: 0,
|
||||
}),
|
||||
}),
|
||||
version: '0.0.0',
|
||||
|
||||
pleroma: ImmutableMap<string, any>({
|
||||
metadata: ImmutableMap<string, any>({
|
||||
account_activation_required: false,
|
||||
|
@ -53,22 +71,15 @@ export const InstanceRecord = ImmutableRecord({
|
|||
enabled: true,
|
||||
exclusions: false,
|
||||
}),
|
||||
description_limit: 1500,
|
||||
}),
|
||||
stats: ImmutableMap(),
|
||||
}),
|
||||
registrations: false,
|
||||
rules: ImmutableList(),
|
||||
short_description: '',
|
||||
stats: ImmutableMap<string, number>({
|
||||
domain_count: 0,
|
||||
status_count: 0,
|
||||
user_count: 0,
|
||||
}),
|
||||
title: '',
|
||||
thumbnail: '',
|
||||
uri: '',
|
||||
urls: ImmutableMap<string, string>(),
|
||||
version: '0.0.0',
|
||||
|
||||
feature_quote: false,
|
||||
fedibird_capabilities: ImmutableList(),
|
||||
|
||||
login_message: '',
|
||||
});
|
||||
|
||||
// Build Mastodon configuration from Pleroma instance
|
||||
|
@ -123,10 +134,40 @@ const fixTakahe = (instance: ImmutableMap<string, any>) => {
|
|||
}
|
||||
};
|
||||
|
||||
/** Convert /api/v1/instance format to v2 */
|
||||
const fixInstanceV1 = (instance: ImmutableMap<string, any>) => {
|
||||
instance.setIn(['configuration', 'urls', 'streaming'], instance.getIn(['urls', 'streaming_api'], ''));
|
||||
|
||||
instance.set('contact', ImmutableMap({
|
||||
account: instance.get('contact_account'),
|
||||
email: instance.get('email'),
|
||||
}));
|
||||
|
||||
instance.set('description', instance.get('short_description') || instance.get('description'));
|
||||
|
||||
instance.set('registrations', ImmutableMap({
|
||||
approval_required: instance.get('approval_required'),
|
||||
enabled: instance.get('registrations'),
|
||||
message: '',
|
||||
}));
|
||||
|
||||
instance.set('thumbnail', ImmutableMap({
|
||||
url: instance.get('thumbnail', ''),
|
||||
}));
|
||||
|
||||
if (instance.has('pleroma')) {
|
||||
instance.setIn(['pleroma', 'metadata', 'description_limit'], instance.get('description_limit'));
|
||||
}
|
||||
};
|
||||
|
||||
// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format
|
||||
export const normalizeInstance = (instance: Record<string, any>) => {
|
||||
return InstanceRecord(
|
||||
ImmutableMap(fromJS(instance)).withMutations((instance: ImmutableMap<string, any>) => {
|
||||
const version = instance.has('domain') ? 'v2' : 'v1';
|
||||
|
||||
if (version === 'v1') fixInstanceV1(instance);
|
||||
|
||||
const { software } = parseVersion(instance.get('version'));
|
||||
const mastodonConfig = pleromaToMastodonConfig(instance);
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ describe('instance reducer', () => {
|
|||
const result = reducer(undefined, {} as any);
|
||||
|
||||
const expected = {
|
||||
description_limit: 1500,
|
||||
configuration: {
|
||||
chats: {
|
||||
max_characters: 500,
|
||||
|
@ -37,7 +36,7 @@ describe('instance reducer', () => {
|
|||
it('normalizes Pleroma instance with Mastodon configuration format', () => {
|
||||
const action = {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: require('soapbox/__fixtures__/pleroma-instance.json'),
|
||||
payload: { instance: require('soapbox/__fixtures__/pleroma-instance.json') },
|
||||
};
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
@ -63,7 +62,7 @@ describe('instance reducer', () => {
|
|||
it('normalizes Mastodon instance with retained configuration', () => {
|
||||
const action = {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: require('soapbox/__fixtures__/mastodon-instance.json'),
|
||||
payload: { instance: require('soapbox/__fixtures__/mastodon-instance.json') },
|
||||
};
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
@ -97,7 +96,7 @@ describe('instance reducer', () => {
|
|||
it('normalizes Mastodon 3.0.0 instance with default configuration', () => {
|
||||
const action = {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: require('soapbox/__fixtures__/mastodon-3.0.0-instance.json'),
|
||||
payload: { instance: require('soapbox/__fixtures__/mastodon-3.0.0-instance.json') },
|
||||
};
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
@ -119,6 +118,40 @@ describe('instance reducer', () => {
|
|||
|
||||
expect(result.toJS()).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('normalizes Mastodon 4.0.2 instance fetched with v2 endpoint', () => {
|
||||
const action = {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: { instance: require('soapbox/__fixtures__/mastodon-instance-v2.json') },
|
||||
};
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
||||
const expected = {
|
||||
configuration: {
|
||||
statuses: {
|
||||
max_characters: 500,
|
||||
max_media_attachments: 4,
|
||||
},
|
||||
polls: {
|
||||
max_options: 4,
|
||||
max_characters_per_option: 50,
|
||||
min_expiration: 300,
|
||||
max_expiration: 2629746,
|
||||
},
|
||||
translation: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
registrations: {
|
||||
enabled: false,
|
||||
approval_required: false,
|
||||
message: null,
|
||||
},
|
||||
};
|
||||
|
||||
expect(result.toJS()).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ADMIN_CONFIG_UPDATE_REQUEST', () => {
|
||||
|
@ -130,13 +163,13 @@ describe('instance reducer', () => {
|
|||
configs,
|
||||
};
|
||||
|
||||
// The normalizer has `registrations: closed` by default
|
||||
// The normalizer has `registrations.enabled: closed` by default
|
||||
const state = reducer(undefined, {} as any);
|
||||
expect(state.registrations).toBe(false);
|
||||
expect(state.registrations.get('enabled')).toBe(false);
|
||||
|
||||
// After importing the configs, registration will be open
|
||||
const result = reducer(state, action);
|
||||
expect(result.registrations).toBe(true);
|
||||
expect(result.registrations.get('enabled')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -65,8 +65,8 @@ const importConfigs = (state: typeof initialState, configs: ImmutableList<any>)
|
|||
const registrationsOpen = getConfigValue(value, ':registrations_open');
|
||||
const approvalRequired = getConfigValue(value, ':account_approval_required');
|
||||
|
||||
state.update('registrations', c => typeof registrationsOpen === 'boolean' ? registrationsOpen : c);
|
||||
state.update('approval_required', c => typeof approvalRequired === 'boolean' ? approvalRequired : c);
|
||||
state.updateIn(['registrations', 'enabled'], c => typeof registrationsOpen === 'boolean' ? registrationsOpen : c);
|
||||
state.updateIn(['registrations', 'approval_required'], c => typeof approvalRequired === 'boolean' ? approvalRequired : c);
|
||||
}
|
||||
|
||||
if (simplePolicy) {
|
||||
|
@ -95,8 +95,7 @@ const getHost = (instance: { uri: string }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const persistInstance = (instance: { uri: string }) => {
|
||||
const host = getHost(instance);
|
||||
const persistInstance = (instance: { uri: string }, host: string | null = getHost(instance)) => {
|
||||
|
||||
if (host) {
|
||||
KVStore.setItem(`instance:${host}`, instance).catch(console.error);
|
||||
|
@ -116,14 +115,14 @@ export default function instance(state = initialState, action: AnyAction) {
|
|||
case PLEROMA_PRELOAD_IMPORT:
|
||||
return preloadImport(state, action, '/api/v1/instance');
|
||||
case rememberInstance.fulfilled.type:
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload.instance)));
|
||||
case fetchInstance.fulfilled.type:
|
||||
persistInstance(action.payload);
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload.instance)));
|
||||
case fetchInstance.rejected.type:
|
||||
return handleInstanceFetchFail(state, action.error);
|
||||
case fetchNodeinfo.fulfilled.type:
|
||||
return importNodeinfo(state, ImmutableMap(fromJS(action.payload)));
|
||||
return importNodeinfo(state, ImmutableMap(fromJS(action.payload.instance)));
|
||||
case ADMIN_CONFIG_UPDATE_REQUEST:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return importConfigs(state, ImmutableList(fromJS(action.configs)));
|
||||
|
|
|
@ -14,7 +14,7 @@ export function connectStream(
|
|||
callbacks: (dispatch: AppDispatch, getState: () => RootState) => Record<string, any> = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} }),
|
||||
) {
|
||||
return (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const streamingAPIBaseURL = getState().instance.urls.get('streaming_api');
|
||||
const streamingAPIBaseURL = getState().instance.configuration.getIn(['urls', 'streaming']) as string;
|
||||
const accessToken = getAccessToken(getState());
|
||||
const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
|
||||
|
||||
|
@ -40,7 +40,7 @@ export function connectStream(
|
|||
// If the WebSocket fails to be created, don't crash the whole page,
|
||||
// just proceed without a subscription.
|
||||
try {
|
||||
subscription = getStream(streamingAPIBaseURL!, accessToken, path, {
|
||||
subscription = getStream(streamingAPIBaseURL, accessToken, path, {
|
||||
connected() {
|
||||
if (pollingRefresh) {
|
||||
clearPolling();
|
||||
|
|
|
@ -802,7 +802,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
|||
* Can translate statuses.
|
||||
* @see POST /api/v1/statuses/:id/translate
|
||||
*/
|
||||
translations: features.includes('translation'),
|
||||
translations: features.includes('translation') || instance.configuration.getIn(['translation', 'enabled']),
|
||||
|
||||
/**
|
||||
* Trending statuses.
|
||||
|
|
Ładowanie…
Reference in New Issue