sforkowany z mirror/soapbox
rodzic
0d42fe1c96
commit
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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -77,7 +77,7 @@ const externalAuthorize = (instance: Instance, baseURL: string) =>
|
|||
|
||||
const externalEthereumLogin = (instance: Instance, baseURL?: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const loginMessage = instance.login_message;
|
||||
const loginMessage = instance.registrations.get('message');
|
||||
|
||||
return getWalletAndSign(loginMessage).then(({ wallet, signature }) => {
|
||||
return dispatch(createExternalApp(instance, baseURL)).then((app) => {
|
||||
|
|
|
@ -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));
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -143,7 +143,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} />
|
||||
)}
|
||||
|
||||
|
|
|
@ -34,8 +34,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" */
|
||||
|
|
|
@ -74,7 +74,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}>
|
||||
|
|
|
@ -32,7 +32,7 @@ const AuthLayout = () => {
|
|||
const soapboxConfig = useSoapboxConfig();
|
||||
|
||||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
const isOpen = features.accountCreation && instance.registrations;
|
||||
const isOpen = features.accountCreation && instance.registrations.get('enabled');
|
||||
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
|
||||
const isLoginPage = history.location.pathname === '/login';
|
||||
const shouldShowRegisterLink = (isLoginPage && (isOpen || (pepeEnabled && pepeOpen)));
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -70,7 +70,9 @@ const Upload: React.FC<IUpload> = ({ composeId, id }) => {
|
|||
const intl = useIntl();
|
||||
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)!;
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ describe('<LandingPage />', () => {
|
|||
const state = rootReducer(undefined, {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: {
|
||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||
registrations: true,
|
||||
instance: {
|
||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||
registrations: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -29,8 +31,10 @@ describe('<LandingPage />', () => {
|
|||
const state = rootReducer(undefined, {
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: {
|
||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||
registrations: false,
|
||||
instance: {
|
||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||
registrations: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -71,8 +75,10 @@ describe('<LandingPage />', () => {
|
|||
const state = applyActions(undefined, [{
|
||||
type: rememberInstance.fulfilled.type,
|
||||
payload: {
|
||||
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
||||
registrations: false,
|
||||
instance: {
|
||||
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
||||
registrations: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
type: PEPE_FETCH_INSTANCE_SUCCESS,
|
||||
|
|
|
@ -89,13 +89,15 @@ const LandingPage = () => {
|
|||
);
|
||||
};
|
||||
|
||||
console.log(instance.registrations.toJS());
|
||||
|
||||
// Render registration flow depending on features
|
||||
const renderBody = () => {
|
||||
if (soapboxConfig.authProvider) {
|
||||
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();
|
||||
|
@ -115,7 +117,7 @@ const LandingPage = () => {
|
|||
|
||||
<Markup
|
||||
size='lg'
|
||||
dangerouslySetInnerHTML={{ __html: instance.short_description || instance.description }}
|
||||
dangerouslySetInnerHTML={{ __html: instance.description }}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
|
|
|
@ -35,7 +35,7 @@ const Header = () => {
|
|||
|
||||
const features = useFeatures();
|
||||
const instance = useInstance();
|
||||
const isOpen = features.accountCreation && instance.registrations;
|
||||
const isOpen = features.accountCreation && instance.registrations.get('enabled');
|
||||
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
|
||||
|
||||
const [isLoading, setLoading] = React.useState(false);
|
||||
|
|
|
@ -28,7 +28,7 @@ const LandingPageModal: React.FC<ILandingPageModal> = ({ onClose }) => {
|
|||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
|
||||
const isOpen = features.accountCreation && instance.registrations;
|
||||
const isOpen = features.accountCreation && instance.registrations.get('enabled');
|
||||
const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true);
|
||||
|
||||
return (
|
||||
|
|
|
@ -335,7 +335,7 @@ const UI: React.FC = ({ 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_api']);
|
||||
const standalone = useAppSelector(isStandalone);
|
||||
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
|
|
|
@ -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: {},
|
||||
polls: {
|
||||
|
@ -19,20 +17,29 @@ 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: {
|
||||
metadata: {
|
||||
account_activation_required: false,
|
||||
birthday_min_age: 0,
|
||||
birthday_required: false,
|
||||
description_limit: 1500,
|
||||
features: [],
|
||||
federation: {
|
||||
enabled: true,
|
||||
|
@ -41,18 +48,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',
|
||||
};
|
||||
|
||||
|
@ -139,7 +151,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));
|
||||
|
@ -151,7 +163,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', () => {
|
||||
|
@ -164,7 +176,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', () => {
|
||||
|
@ -177,7 +189,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>(),
|
||||
polls: ImmutableMap<string, number>({
|
||||
|
@ -31,15 +33,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,
|
||||
|
@ -50,22 +68,13 @@ 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(),
|
||||
});
|
||||
|
||||
// Build Mastodon configuration from Pleroma instance
|
||||
|
@ -109,10 +118,39 @@ const fixAkkoma = (instance: ImmutableMap<string, any>) => {
|
|||
}
|
||||
};
|
||||
|
||||
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.get('login_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: {
|
||||
statuses: {
|
||||
max_characters: 500,
|
||||
|
@ -34,7 +33,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);
|
||||
|
@ -60,7 +59,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);
|
||||
|
@ -94,7 +93,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);
|
||||
|
@ -116,6 +115,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', () => {
|
||||
|
@ -127,13 +160,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();
|
||||
|
|
|
@ -628,7 +628,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