Merge branch 'normal-account-refactor' into 'develop'

Remove importer/normalizer.js

See merge request soapbox-pub/soapbox-fe!1094
This commit is contained in:
Alex Gleason 2022-03-12 22:11:18 +00:00
commit fcded3aae6
22 changed files with 718 additions and 132 deletions

View file

@ -0,0 +1,46 @@
{
"id": "106801667066418367",
"username": "benis911",
"acct": "benis911",
"display_name": "",
"locked": false,
"bot": false,
"discoverable": null,
"group": false,
"created_at": "2021-08-22T00:00:00.000Z",
"note": "",
"url": "https://mastodon.social/@benis911",
"avatar": "https://mastodon.social/avatars/original/missing.png",
"avatar_static": "https://mastodon.social/avatars/original/missing.png",
"header": "https://mastodon.social/headers/original/missing.png",
"header_static": "https://mastodon.social/headers/original/missing.png",
"followers_count": 0,
"following_count": 0,
"statuses_count": 5,
"last_status_at": "2022-02-23",
"moved": {
"id": "107945464165013501",
"username": "alex",
"acct": "alex@fedibird.com",
"display_name": "",
"locked": false,
"bot": false,
"discoverable": false,
"group": false,
"created_at": "2020-01-27T00:00:00.000Z",
"note": "<p></p>",
"url": "https://fedibird.com/@alex",
"avatar": "https://mastodon.social/avatars/original/missing.png",
"avatar_static": "https://mastodon.social/avatars/original/missing.png",
"header": "https://mastodon.social/headers/original/missing.png",
"header_static": "https://mastodon.social/headers/original/missing.png",
"followers_count": 1,
"following_count": 1,
"statuses_count": 5,
"last_status_at": null,
"emojis": [],
"fields": []
},
"emojis": [],
"fields": []
}

View file

@ -0,0 +1,140 @@
{
"acct": "alex",
"avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
"avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
"bot": false,
"created_at": "2020-01-08T01:25:43.000Z",
"display_name": "Alex Gleason 😂 :soapbox: :ablobcatrainbow:",
"emojis": [
{
"shortcode": "ablobcatrainbow",
"static_url": "https://gleasonator.com/emoji/blobcat/ablobcatrainbow.png",
"url": "https://gleasonator.com/emoji/blobcat/ablobcatrainbow.png",
"visible_in_picker": false
},
{
"shortcode": "soapbox",
"static_url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
"url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
"visible_in_picker": false
}
],
"fields": [
{
"name": "Website",
"value": "<a href=\"https://alexgleason.me\" rel=\"ugc\">https://alexgleason.me</a>"
},
{
"name": "Soapbox :ablobcatrainbow:",
"value": "<a href=\"https://soapbox.pub\" rel=\"ugc\">https://soapbox.pub</a> :soapbox:"
},
{
"name": "Email",
"value": "alex@alexgleason.me"
},
{
"name": "Gender identity",
"value": "Soyboy"
},
{
"name": "Donate (PayPal)",
"value": "<a href=\"https://paypal.me/gleasonator\" rel=\"ugc\">https://paypal.me/gleasonator</a>"
},
{
"name": "$BTC",
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
},
{
"name": "$ETH",
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
},
{
"name": "$DOGE",
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
},
{
"name": "$XMR",
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
}
],
"followers_count": 2476,
"following_count": 1584,
"fqn": "alex@gleasonator.com",
"header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
"header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
"id": "9v5bmRalQvjOy0ECcC",
"last_status_at": "2022-03-12T16:35:10",
"locked": false,
"note": "I create Fediverse software that empowers people online. :soapbox:<br/><br/>I&#39;m vegan btw<br/><br/>Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
"pleroma": {
"accepts_chat_messages": true,
"also_known_as": [
"https://mitra.social/users/alex"
],
"ap_id": "https://gleasonator.com/users/alex",
"background_image": null,
"birthday": "1993-07-03",
"favicon": "https://gleasonator.com/favicon.png",
"hide_favorites": true,
"hide_followers": false,
"hide_followers_count": false,
"hide_follows": false,
"hide_follows_count": false,
"is_admin": true,
"is_confirmed": true,
"is_moderator": false,
"is_suggested": true,
"relationship": {},
"skip_thread_containment": false,
"tags": []
},
"source": {
"fields": [
{
"name": "Website",
"value": "https://alexgleason.me"
},
{
"name": "Soapbox :ablobcatrainbow:",
"value": "https://soapbox.pub :soapbox:"
},
{
"name": "Email",
"value": "alex@alexgleason.me"
},
{
"name": "Gender identity",
"value": "Soyboy"
},
{
"name": "Donate (PayPal)",
"value": "https://paypal.me/gleasonator"
},
{
"name": "$BTC",
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
},
{
"name": "$ETH",
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
},
{
"name": "$DOGE",
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
},
{
"name": "$XMR",
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
}
],
"note": "I create Fediverse software that empowers people online. :soapbox:\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
"pleroma": {
"actor_type": "Person",
"discoverable": false
},
"sensitive": false
},
"statuses_count": 23674,
"url": "https://gleasonator.com/users/alex",
"username": "alex"
}

View file

@ -0,0 +1,210 @@
{
"account": {
"acct": "alex",
"avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
"avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
"bot": false,
"created_at": "2020-01-08T01:25:43.000Z",
"display_name": "Alex Gleason",
"emojis": [
{
"shortcode": "soapbox",
"static_url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
"url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
"visible_in_picker": false
}
],
"fields": [
{
"name": "Website",
"value": "<a href=\"https://alexgleason.me\" rel=\"ugc\">https://alexgleason.me</a>"
},
{
"name": "Soapbox :soapbox:",
"value": "<a href=\"https://soapbox.pub\" rel=\"ugc\">https://soapbox.pub</a>"
},
{
"name": "Email",
"value": "alex@alexgleason.me"
},
{
"name": "Gender identity",
"value": "Soyboy"
},
{
"name": "Donate (PayPal)",
"value": "<a href=\"https://paypal.me/gleasonator\" rel=\"ugc\">https://paypal.me/gleasonator</a>"
},
{
"name": "$BTC",
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
},
{
"name": "$ETH",
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
},
{
"name": "$DOGE",
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
},
{
"name": "$XMR",
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
}
],
"followers_count": 2476,
"following_count": 1584,
"fqn": "alex@gleasonator.com",
"header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
"header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
"id": "9v5bmRalQvjOy0ECcC",
"last_status_at": "2022-03-12T16:35:10",
"locked": false,
"note": "I create Fediverse software that empowers people online. :soapbox:<br/><br/>I&#39;m vegan btw<br/><br/>Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
"pleroma": {
"accepts_chat_messages": true,
"also_known_as": [
"https://mitra.social/users/alex"
],
"ap_id": "https://gleasonator.com/users/alex",
"background_image": null,
"birthday": "1993-07-03",
"favicon": "https://gleasonator.com/favicon.png",
"hide_favorites": true,
"hide_followers": false,
"hide_followers_count": false,
"hide_follows": false,
"hide_follows_count": false,
"is_admin": true,
"is_confirmed": true,
"is_moderator": false,
"is_suggested": true,
"relationship": {},
"skip_thread_containment": false,
"tags": []
},
"source": {
"fields": [
{
"name": "Website",
"value": "https://alexgleason.me"
},
{
"name": "Soapbox :soapbox:",
"value": "https://soapbox.pub"
},
{
"name": "Email",
"value": "alex@alexgleason.me"
},
{
"name": "Gender identity",
"value": "Soyboy"
},
{
"name": "Donate (PayPal)",
"value": "https://paypal.me/gleasonator"
},
{
"name": "$BTC",
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
},
{
"name": "$ETH",
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
},
{
"name": "$DOGE",
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
},
{
"name": "$XMR",
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
}
],
"note": "I create Fediverse software that empowers people online. :soapbox:\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
"pleroma": {
"actor_type": "Person",
"discoverable": false
},
"sensitive": false
},
"statuses_count": 23674,
"url": "https://gleasonator.com/users/alex",
"username": "alex"
},
"application": null,
"bookmarked": false,
"card": {
"author_name": "Alex Gleason",
"author_url": "https://soapbox.pub/author/alex/",
"blurhash": null,
"description": "On cryptocurrency Ive always believed integrated donations would be a necessary part of the Fediverse. Admins do all the heavy lifting; its a thankless job. Meanwhile users want to help secure their new online home, but feel powerless to do so. I have been running an experimental payment platform based on Stripe alongside my Soapbox […]",
"embed_url": null,
"height": 338,
"html": "",
"image": "https://gleasonator.com/proxy/L2kUi5uxMdoC6LYYrnAdlJviPGQ/aHR0cHM6Ly9tZWRpYS5zb2FwYm94LnB1Yi91cGxvYWRzLzIwMjEvMDcvdi0xLTMtdGh1bWIucG5n/v-1-3-thumb.png",
"provider_name": "Soapbox",
"provider_url": "https://soapbox.pub",
"title": "Soapbox FE v1.3: The Crypto Release - Soapbox",
"type": "link",
"url": "https://soapbox.pub/blog/soapbox-fe-v1.3-cryptocurrency-release/",
"width": 600
},
"content": "<p>Soapbox FE v1.3 released. Read about it here: <a href=\"https://soapbox.pub/blog/soapbox-fe-v1.3-cryptocurrency-release/\">https://soapbox.pub/blog/soapbox-fe-v1.3-cryptocurrency-release/</a></p><p>Enjoy!</p>",
"created_at": "2021-07-02T20:49:39.000Z",
"emojis": [],
"favourited": false,
"favourites_count": 29,
"id": "A8tEMYF2GNnfPcL4dc",
"in_reply_to_account_id": null,
"in_reply_to_id": null,
"language": null,
"media_attachments": [],
"mentions": [],
"muted": false,
"pinned": true,
"pleroma": {
"content": {
"text/plain": "Soapbox FE v1.3 released. Read about it here: https://soapbox.pub/blog/soapbox-fe-v1.3-cryptocurrency-release/Enjoy!"
},
"conversation_id": "16496668",
"direct_conversation_id": null,
"emoji_reactions": [
{
"count": 5,
"me": false,
"name": "❤️"
},
{
"count": 1,
"me": false,
"name": "👍"
}
],
"expires_at": null,
"in_reply_to_account_acct": null,
"local": true,
"parent_visible": false,
"pinned_at": "2021-11-23T01:38:44.000Z",
"quote": null,
"quote_url": null,
"quote_visible": false,
"spoiler_text": {
"text/plain": ""
},
"thread_muted": false
},
"poll": null,
"reblog": null,
"reblogged": false,
"reblogs_count": 16,
"replies_count": 7,
"sensitive": false,
"spoiler_text": "",
"tags": [],
"text": null,
"uri": "https://gleasonator.com/objects/3eabaf63-47f4-4314-9ddb-ce7dbf46b393",
"url": "https://gleasonator.com/notice/A8tEMYF2GNnfPcL4dc",
"visibility": "public"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,12 +1,22 @@
/**
* Account normalizer:
* Converts API accounts into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/account/}
*/
import escapeTextContentForBrowser from 'escape-html';
import {
Map as ImmutableMap,
List as ImmutableList,
Record as ImmutableRecord,
} from 'immutable';
import emojify from 'soapbox/features/emoji/emoji';
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
import { IAccount } from 'soapbox/types';
import { mergeDefined } from 'soapbox/utils/normalizers';
import { unescapeHTML } from 'soapbox/utils/html';
import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers';
// https://docs.joinmastodon.org/entities/account/
const AccountRecord = ImmutableRecord({
acct: '',
avatar: '',
@ -45,6 +55,18 @@ const AccountRecord = ImmutableRecord({
should_refetch: false,
});
// https://docs.joinmastodon.org/entities/field/
const FieldRecord = ImmutableRecord({
name: '',
value: '',
verified_at: null,
// Internal fields
name_emojified: '',
value_emojified: '',
value_plain: '',
});
// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/549
const normalizePleromaLegacyFields = (account: ImmutableMap<string, any>) => {
return account.update('pleroma', ImmutableMap(), (pleroma: ImmutableMap<string, any>) => {
@ -61,6 +83,29 @@ const normalizePleromaLegacyFields = (account: ImmutableMap<string, any>) => {
});
};
// Add avatar, if missing
const normalizeAvatar = (account: ImmutableMap<string, any>) => {
const avatar = account.get('avatar');
const avatarStatic = account.get('avatar_static');
const missing = require('images/avatar-missing.png');
return account.withMutations(account => {
account.set('avatar', avatar || avatarStatic || missing);
account.set('avatar_static', avatarStatic || avatar || missing);
});
};
// Normalize custom fields
const normalizeFields = (account: ImmutableMap<string, any>) => {
return account.update('fields', ImmutableList(), fields => fields.map(FieldRecord));
};
// Normalize emojis
const normalizeEmojis = (entity: ImmutableMap<string, any>) => {
const emojis = entity.get('emojis', ImmutableList()).map(normalizeEmoji);
return entity.set('emojis', emojis);
};
// Normalize Pleroma/Fedibird birthday
const normalizeBirthday = (account: ImmutableMap<string, any>) => {
const birthday = [
@ -99,19 +144,55 @@ const normalizeLocation = (account: ImmutableMap<string, any>) => {
// Set username from acct, if applicable
const fixUsername = (account: ImmutableMap<string, any>) => {
return account.update('username', username => (
username || (account.get('acct') || '').split('@')[0]
));
const acct = account.get('acct') || '';
const username = account.get('username') || '';
return account.set('username', username || acct.split('@')[0]);
};
// Set display name from username, if applicable
const fixDisplayName = (account: ImmutableMap<string, any>) => {
const displayName = account.get('display_name') || '';
return account.set('display_name', displayName.trim().length === 0 ? account.get('username') : displayName);
};
// Emojification, etc
const addInternalFields = (account: ImmutableMap<string, any>) => {
const emojiMap = makeEmojiMap(account.get('emojis'));
return account.withMutations((account: ImmutableMap<string, any>) => {
// Emojify account properties
account.merge({
display_name_html: emojify(escapeTextContentForBrowser(account.get('display_name')), emojiMap),
note_emojified: emojify(account.get('note', ''), emojiMap),
note_plain: unescapeHTML(account.get('note', '')),
});
// Emojify fields
account.update('fields', ImmutableList(), fields => {
return fields.map((field: ImmutableMap<string, any>) => {
return field.merge({
name_emojified: emojify(escapeTextContentForBrowser(field.get('name')), emojiMap),
value_emojified: emojify(field.get('value'), emojiMap),
value_plain: unescapeHTML(field.get('value')),
});
});
});
});
};
export const normalizeAccount = (account: ImmutableMap<string, any>): IAccount => {
return AccountRecord(
account.withMutations(account => {
normalizePleromaLegacyFields(account);
normalizeEmojis(account);
normalizeAvatar(account);
normalizeFields(account);
normalizeVerified(account);
normalizeBirthday(account);
normalizeLocation(account);
fixUsername(account);
fixDisplayName(account);
addInternalFields(account);
}),
);
};

View file

@ -0,0 +1,45 @@
/**
* Attachment normalizer:
* Converts API attachments into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/attachment/}
*/
import {
Map as ImmutableMap,
Record as ImmutableRecord,
} from 'immutable';
import { mergeDefined } from 'soapbox/utils/normalizers';
// https://docs.joinmastodon.org/entities/attachment/
const AttachmentRecord = ImmutableRecord({
blurhash: undefined,
description: '',
id: '',
meta: ImmutableMap(),
pleroma: ImmutableMap(),
preview_url: '',
remote_url: null,
type: 'unknown',
url: '',
// Internal fields
// TODO: Remove these? They're set in selectors/index.js
account: null,
status: null,
});
// Ensure attachments have required fields
export const normalizeAttachment = (attachment: ImmutableMap<string, any>) => {
const url = [
attachment.get('url'),
attachment.get('preview_url'),
attachment.get('remote_url'),
].find(url => url) || '';
const base = ImmutableMap({
url,
preview_url: url,
});
return AttachmentRecord(attachment.mergeWith(mergeDefined, base));
};

View file

@ -0,0 +1,28 @@
/**
* Card normalizer:
* Converts API cards into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/card/}
*/
import { Record as ImmutableRecord, Map as ImmutableMap } from 'immutable';
// https://docs.joinmastodon.org/entities/card/
const CardRecord = ImmutableRecord({
author_name: '',
author_url: '',
blurhash: null,
description: '',
embed_url: '',
height: 0,
html: '',
image: null,
provider_name: '',
provider_url: '',
title: '',
type: 'link',
url: '',
width: 0,
});
export const normalizeCard = (card: ImmutableMap<string, any>) => {
return CardRecord(card);
};

View file

@ -0,0 +1,19 @@
/**
* Emoji normalizer:
* Converts API emojis into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/emoji/}
*/
import { Record as ImmutableRecord, Map as ImmutableMap } from 'immutable';
// https://docs.joinmastodon.org/entities/emoji/
const EmojiRecord = ImmutableRecord({
category: '',
shortcode: '',
static_url: '',
url: '',
visible_in_picker: true,
});
export const normalizeEmoji = (emoji: ImmutableMap<string, any>) => {
return EmojiRecord(emoji);
};

View file

@ -1,3 +1,8 @@
/**
* Instance normalizer:
* Converts API instances into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/instance/}
*/
import {
Map as ImmutableMap,
List as ImmutableList,
@ -9,6 +14,7 @@ import { mergeDefined } from 'soapbox/utils/normalizers';
import { isNumber } from 'soapbox/utils/numbers';
// Use Mastodon defaults
// https://docs.joinmastodon.org/entities/instance/
const InstanceRecord = ImmutableRecord({
approval_required: false,
contact_account: ImmutableMap(),

View file

@ -0,0 +1,24 @@
/**
* Mention normalizer:
* Converts API mentions into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/mention/}
*/
import {
Map as ImmutableMap,
Record as ImmutableRecord,
} from 'immutable';
import { normalizeAccount } from 'soapbox/normalizers/account';
// https://docs.joinmastodon.org/entities/mention/
const MentionRecord = ImmutableRecord({
id: '',
acct: '',
username: '',
url: '',
});
export const normalizeMention = (mention: ImmutableMap<string, any>) => {
// Simply normalize it as an account then cast it as a mention ¯\_(ツ)_/¯
return MentionRecord(normalizeAccount(mention));
};

View file

@ -1,3 +1,8 @@
/**
* Notification normalizer:
* Converts API notifications into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/notification/}
*/
import {
Map as ImmutableMap,
Record as ImmutableRecord,

View file

@ -0,0 +1,88 @@
/**
* Poll normalizer:
* Converts API polls into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/poll/}
*/
import escapeTextContentForBrowser from 'escape-html';
import {
Map as ImmutableMap,
List as ImmutableList,
Record as ImmutableRecord,
} from 'immutable';
import emojify from 'soapbox/features/emoji/emoji';
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
import { makeEmojiMap } from 'soapbox/utils/normalizers';
// https://docs.joinmastodon.org/entities/poll/
const PollRecord = ImmutableRecord({
emojis: ImmutableList(),
expired: false,
expires_at: new Date(),
id: '',
multiple: false,
options: ImmutableList(),
voters_count: 0,
votes_count: 0,
own_votes: null,
voted: false,
});
// Sub-entity of Poll
const PollOptionRecord = ImmutableRecord({
title: '',
votes_count: 0,
// Internal fields
title_emojified: '',
});
// Normalize emojis
const normalizeEmojis = (entity: ImmutableMap<string, any>) => {
return entity.update('emojis', ImmutableList(), emojis => {
return emojis.map(normalizeEmoji);
});
};
const normalizePollOption = (option: ImmutableMap<string, any>, emojis: ImmutableList<ImmutableMap<string, string>> = ImmutableList()) => {
const emojiMap = makeEmojiMap(emojis);
const titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
return PollOptionRecord(
option.set('title_emojified', titleEmojified),
);
};
// Normalize poll options
const normalizePollOptions = (poll: ImmutableMap<string, any>) => {
const emojis = poll.get('emojis');
return poll.update('options', (options: ImmutableList<ImmutableMap<string, any>>) => {
return options.map(option => normalizePollOption(option, emojis));
});
};
// Normalize own_votes to `null` if empty (like Mastodon)
const normalizePollOwnVotes = (poll: ImmutableMap<string, any>) => {
return poll.update('own_votes', ownVotes => {
return ownVotes?.size > 0 ? ownVotes : null;
});
};
// Whether the user voted in the poll
const normalizePollVoted = (poll: ImmutableMap<string, any>) => {
return poll.update('voted', voted => {
return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0;
});
};
export const normalizePoll = (poll: ImmutableMap<string, any>) => {
return PollRecord(
poll.withMutations((poll: ImmutableMap<string, any>) => {
normalizeEmojis(poll);
normalizePollOptions(poll);
normalizePollOwnVotes(poll);
normalizePollVoted(poll);
}),
);
};

View file

@ -1,15 +1,22 @@
import escapeTextContentForBrowser from 'escape-html';
/**
* Status normalizer:
* Converts API statuses into our internal format.
* @see {@link https://docs.joinmastodon.org/entities/status/}
*/
import {
Map as ImmutableMap,
List as ImmutableList,
Record as ImmutableRecord,
} from 'immutable';
import emojify from 'soapbox/features/emoji/emoji';
import { normalizeAccount } from 'soapbox/normalizers/account';
import { normalizeAttachment } from 'soapbox/normalizers/attachment';
import { normalizeCard } from 'soapbox/normalizers/card';
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
import { normalizeMention } from 'soapbox/normalizers/mention';
import { normalizePoll } from 'soapbox/normalizers/poll';
import { IStatus } from 'soapbox/types';
import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers';
// https://docs.joinmastodon.org/entities/status/
const StatusRecord = ImmutableRecord({
account: null,
application: null,
@ -49,91 +56,12 @@ const StatusRecord = ImmutableRecord({
spoilerHtml: '',
});
// https://docs.joinmastodon.org/entities/attachment/
const AttachmentRecord = ImmutableRecord({
blurhash: undefined,
description: '',
id: '',
meta: ImmutableMap(),
pleroma: ImmutableMap(),
preview_url: '',
remote_url: null,
type: 'unknown',
url: '',
// Internal fields
account: null,
status: null,
});
// https://docs.joinmastodon.org/entities/mention/
const MentionRecord = ImmutableRecord({
id: '',
acct: '',
username: '',
url: '',
});
// https://docs.joinmastodon.org/entities/poll/
const PollRecord = ImmutableRecord({
emojis: ImmutableList(),
expired: false,
expires_at: new Date(),
id: '',
multiple: false,
options: ImmutableList(),
voters_count: 0,
votes_count: 0,
own_votes: null,
voted: false,
});
// Sub-entity of Poll
const PollOptionRecord = ImmutableRecord({
title: '',
votes_count: 0,
// Internal fields
title_emojified: '',
});
// https://docs.joinmastodon.org/entities/emoji/
const EmojiRecord = ImmutableRecord({
category: '',
shortcode: '',
static_url: '',
url: '',
visible_in_picker: true,
});
// Ensure attachments have required fields
// https://docs.joinmastodon.org/entities/attachment/
const normalizeAttachment = (attachment: ImmutableMap<string, any>) => {
const url = [
attachment.get('url'),
attachment.get('preview_url'),
attachment.get('remote_url'),
].find(url => url) || '';
const base = ImmutableMap({
url,
preview_url: url,
});
return AttachmentRecord(attachment.mergeWith(mergeDefined, base));
};
const normalizeAttachments = (status: ImmutableMap<string, any>) => {
return status.update('media_attachments', ImmutableList(), attachments => {
return attachments.map(normalizeAttachment);
});
};
// Normalize mentions
const normalizeMention = (mention: ImmutableMap<string, any>) => {
return MentionRecord(normalizeAccount(mention));
};
const normalizeMentions = (status: ImmutableMap<string, any>) => {
return status.update('mentions', ImmutableList(), mentions => {
return mentions.map(normalizeMention);
@ -143,54 +71,10 @@ const normalizeMentions = (status: ImmutableMap<string, any>) => {
// Normalize emojis
const normalizeEmojis = (entity: ImmutableMap<string, any>) => {
return entity.update('emojis', ImmutableList(), emojis => {
return emojis.map(EmojiRecord);
return emojis.map(normalizeEmoji);
});
};
const normalizePollOption = (option: ImmutableMap<string, any>, emojis: ImmutableList<ImmutableMap<string, string>> = ImmutableList()) => {
const emojiMap = makeEmojiMap(emojis);
const titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
return PollOptionRecord(
option.set('title_emojified', titleEmojified),
);
};
// Normalize poll options
const normalizePollOptions = (poll: ImmutableMap<string, any>) => {
const emojis = poll.get('emojis');
return poll.update('options', (options: ImmutableList<ImmutableMap<string, any>>) => {
return options.map(option => normalizePollOption(option, emojis));
});
};
// Normalize own_votes to `null` if empty (like Mastodon)
const normalizePollOwnVotes = (poll: ImmutableMap<string, any>) => {
return poll.update('own_votes', ownVotes => {
return ownVotes?.size > 0 ? ownVotes : null;
});
};
// Whether the user voted in the poll
const normalizePollVoted = (poll: ImmutableMap<string, any>) => {
return poll.update('voted', voted => {
return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0;
});
};
// Normalize the actual poll
const normalizePoll = (poll: ImmutableMap<string, any>) => {
return PollRecord(
poll.withMutations((poll: ImmutableMap<string, any>) => {
normalizeEmojis(poll);
normalizePollOptions(poll);
normalizePollOwnVotes(poll);
normalizePollVoted(poll);
}),
);
};
// Normalize the poll in the status, if applicable
const normalizeStatusPoll = (status: ImmutableMap<string, any>) => {
if (status.hasIn(['poll', 'options'])) {
@ -200,6 +84,15 @@ const normalizeStatusPoll = (status: ImmutableMap<string, any>) => {
}
};
// Normalize card
const normalizeStatusCard = (status: ImmutableMap<string, any>) => {
if (status.get('card')) {
return status.update('card', ImmutableMap(), normalizeCard);
} else {
return status.set('card', null);
}
};
// Fix order of mentions
const fixMentionsOrder = (status: ImmutableMap<string, any>) => {
const mentions = status.get('mentions', ImmutableList());
@ -249,6 +142,7 @@ export const normalizeStatus = (status: ImmutableMap<string, any>): IStatus => {
normalizeMentions(status);
normalizeEmojis(status);
normalizeStatusPoll(status);
normalizeStatusCard(status);
fixMentionsOrder(status);
addSelfMention(status);
fixQuote(status);

Binary file not shown.