pleroma-fe/src/services/entity_normalizer/entity_normalizer.service.js

521 lines
16 KiB
JavaScript
Raw Normal View History

import escape from 'escape-html'
import parseLinkHeader from 'parse-link-header'
import { isStatusNotification } from '../notification_utils/notification_utils.js'
import punycode from 'punycode.js'
/** NOTICE! **
* Do not initialize UI-generated data here.
* It will override existing data.
*
* i.e. user.pinnedStatusIds was set to [] here
* UI code would update it with data but upon next user fetch
* it would be reverted back to []
*/
2019-01-14 13:30:14 +01:00
const qvitterStatusType = (status) => {
if (status.is_post_verb) {
return 'status'
}
if (status.retweeted_status) {
return 'retweet'
}
if ((typeof status.uri === 'string' && status.uri.match(/(fave|objectType=Favourite)/)) ||
(typeof status.text === 'string' && status.text.match(/favorited/))) {
return 'favorite'
}
if (status.text.match(/deleted notice {{tag/) || status.qvitter_delete_notice) {
return 'deletion'
}
if (status.text.match(/started following/) || status.activity_type === 'follow') {
return 'follow'
}
return 'unknown'
}
2019-01-14 13:30:14 +01:00
export const parseUser = (data) => {
const output = {}
2022-07-31 11:36:02 +02:00
const masto = Object.prototype.hasOwnProperty.call(data, 'acct')
2019-01-14 13:30:14 +01:00
// case for users in "mentions" property for statuses in MastoAPI
2022-07-31 11:36:02 +02:00
const mastoShort = masto && !Object.prototype.hasOwnProperty.call(data, 'avatar')
2019-01-14 13:30:14 +01:00
output.inLists = null
output.id = String(data.id)
output._original = data // used for server-side settings
2019-01-14 13:30:14 +01:00
if (masto) {
output.screen_name = data.acct
2022-08-12 00:19:19 +02:00
output.fqn = data.fqn
output.statusnet_profile_url = data.url
2019-01-14 13:30:14 +01:00
// There's nothing else to get
if (mastoShort) {
return output
}
output.emoji = data.emojis
output.name = escape(data.display_name)
output.name_html = output.name
output.name_unescaped = data.display_name
2019-01-14 13:30:14 +01:00
output.description = data.note
// TODO cleanup this shit, output.description is overriden with source data
output.description_html = data.note
2019-01-14 13:30:14 +01:00
2019-11-19 15:15:41 +01:00
output.fields = data.fields
output.fields_html = data.fields.map(field => {
return {
name: escape(field.name),
value: field.value
2019-11-19 15:15:41 +01:00
}
})
2020-02-19 12:57:58 +01:00
output.fields_text = data.fields.map(field => {
return {
2020-06-17 17:26:06 +02:00
name: unescape(field.name.replace(/<[^>]*>/g, '')),
value: unescape(field.value.replace(/<[^>]*>/g, ''))
2020-02-19 12:57:58 +01:00
}
})
2019-11-19 15:15:41 +01:00
2019-01-14 13:30:14 +01:00
// Utilize avatar_static for gif avatars?
output.profile_image_url = data.avatar
output.profile_image_url_original = data.avatar
// Same, utilize header_static?
output.cover_photo = data.header
output.friends_count = data.following_count
output.bot = data.bot
if (data.pleroma) {
2022-08-04 00:56:52 +02:00
if (data.pleroma.settings_store) {
output.storage = data.pleroma.settings_store['pleroma-fe']
}
const relationship = data.pleroma.relationship
output.background_image = data.pleroma.background_image
output.favicon = data.pleroma.favicon
output.token = data.pleroma.chat_token
2020-04-21 22:27:51 +02:00
if (relationship) {
output.relationship = relationship
}
2019-02-18 15:49:32 +01:00
2020-02-03 20:26:32 +01:00
output.allow_following_move = data.pleroma.allow_following_move
output.hide_follows = data.pleroma.hide_follows
output.hide_followers = data.pleroma.hide_followers
output.hide_follows_count = data.pleroma.hide_follows_count
output.hide_followers_count = data.pleroma.hide_followers_count
2019-02-18 15:49:32 +01:00
output.rights = {
moderator: data.pleroma.is_moderator,
admin: data.pleroma.is_admin
}
2019-05-21 22:35:40 +02:00
// TODO: Clean up in UI? This is duplication from what BE does for qvitterapi
if (output.rights.admin) {
output.role = 'admin'
} else if (output.rights.moderator) {
output.role = 'moderator'
} else {
output.role = 'member'
}
output.birthday = data.pleroma.birthday
if (data.pleroma.privileges) {
output.privileges = data.pleroma.privileges
} else if (data.pleroma.is_admin) {
output.privileges = [
'users_read',
'users_manage_invites',
'users_manage_activation_state',
'users_manage_tags',
'users_manage_credentials',
'users_delete',
'messages_read',
'messages_delete',
'instances_delete',
'reports_manage_reports',
'moderation_log_read',
'announcements_manage_announcements',
'emoji_manage_emoji',
'statistics_read'
]
} else if (data.pleroma.is_moderator) {
output.privileges = [
'messages_delete',
'reports_manage_reports'
]
} else {
output.privileges = []
}
2019-05-21 22:35:40 +02:00
}
if (data.source) {
output.description = data.source.note
output.default_scope = data.source.privacy
2019-11-19 15:15:41 +01:00
output.fields = data.source.fields
2019-05-21 22:35:40 +02:00
if (data.source.pleroma) {
output.no_rich_text = data.source.pleroma.no_rich_text
output.show_role = data.source.pleroma.show_role
output.discoverable = data.source.pleroma.discoverable
output.show_birthday = data.pleroma.show_birthday
2019-05-21 22:35:40 +02:00
}
}
2019-01-17 21:53:05 +01:00
// TODO: handle is_local
2019-03-13 20:43:48 +01:00
output.is_local = !output.screen_name.includes('@')
2019-01-14 13:30:14 +01:00
} else {
output.screen_name = data.screen_name
output.name = data.name
output.name_html = data.name_html
output.description = data.description
output.description_html = data.description_html
output.profile_image_url = data.profile_image_url
output.profile_image_url_original = data.profile_image_url_original
output.cover_photo = data.cover_photo
output.friends_count = data.friends_count
// output.bot = ??? missing
2019-01-14 13:30:14 +01:00
output.statusnet_profile_url = data.statusnet_profile_url
2019-01-17 21:53:05 +01:00
2019-01-14 13:30:14 +01:00
output.is_local = data.is_local
output.role = data.role
output.show_role = data.show_role
2019-01-17 20:11:51 +01:00
2019-02-18 15:49:32 +01:00
if (data.rights) {
output.rights = {
moderator: data.rights.delete_others_notice,
admin: data.rights.admin
}
}
2019-01-17 20:11:51 +01:00
output.no_rich_text = data.no_rich_text
output.default_scope = data.default_scope
output.hide_follows = data.hide_follows
output.hide_followers = data.hide_followers
output.hide_follows_count = data.hide_follows_count
output.hide_followers_count = data.hide_followers_count
2019-01-17 20:11:51 +01:00
output.background_image = data.background_image
// Websocket token
output.token = data.token
2020-04-21 22:27:51 +02:00
// Convert relationsip data to expected format
output.relationship = {
muting: data.muted,
blocking: data.statusnet_blocking,
followed_by: data.follows_you,
following: data.following
}
}
2019-01-14 13:30:14 +01:00
output.created_at = new Date(data.created_at)
output.locked = data.locked
output.followers_count = data.followers_count
output.statuses_count = data.statuses_count
if (data.pleroma) {
output.follow_request_count = data.pleroma.follow_request_count
2019-01-14 13:30:14 +01:00
2019-02-18 15:49:32 +01:00
output.tags = data.pleroma.tags
2021-01-19 17:01:55 +01:00
// deactivated was changed to is_active in Pleroma 2.3.0
2021-01-19 17:01:55 +01:00
// so check if is_active is present
2021-01-20 17:04:49 +01:00
output.deactivated = typeof data.pleroma.is_active !== 'undefined'
2021-01-19 17:01:55 +01:00
? !data.pleroma.is_active // new backend
: data.pleroma.deactivated // old backend
2019-02-18 15:49:32 +01:00
2019-05-25 09:01:02 +02:00
output.notification_settings = data.pleroma.notification_settings
2020-05-07 15:10:53 +02:00
output.unread_chat_count = data.pleroma.unread_chat_count
2019-02-18 15:49:32 +01:00
}
output.tags = output.tags || []
output.rights = output.rights || {}
2019-05-25 09:01:02 +02:00
output.notification_settings = output.notification_settings || {}
2019-02-18 15:49:32 +01:00
// Convert punycode to unicode for UI
output.screen_name_ui = output.screen_name
if (output.screen_name && output.screen_name.includes('@')) {
const parts = output.screen_name.split('@')
const unicodeDomain = punycode.toUnicode(parts[1])
if (unicodeDomain !== parts[1]) {
// Add some identifier so users can potentially spot spoofing attempts:
// lain.com and xn--lin-6cd.com would appear identical otherwise.
output.screen_name_ui_contains_non_ascii = true
output.screen_name_ui = [parts[0], unicodeDomain].join('@')
} else {
output.screen_name_ui_contains_non_ascii = false
}
}
2019-01-14 13:30:14 +01:00
return output
}
export const parseAttachment = (data) => {
2019-01-21 14:28:36 +01:00
const output = {}
2022-07-31 11:36:02 +02:00
const masto = !Object.prototype.hasOwnProperty.call(data, 'oembed')
2019-01-21 14:28:36 +01:00
if (masto) {
// Not exactly same...
output.mimetype = data.pleroma ? data.pleroma.mime_type : data.type
2019-01-21 14:28:36 +01:00
output.meta = data.meta // not present in BE yet
2019-03-25 17:19:33 +01:00
output.id = data.id
2019-01-21 14:28:36 +01:00
} else {
output.mimetype = data.mimetype
// output.meta = ??? missing
}
2019-01-21 14:28:36 +01:00
output.url = data.url
output.large_thumb_url = data.preview_url
2019-01-21 14:28:36 +01:00
output.description = data.description
return output
}
2022-06-08 05:31:48 +02:00
export const parseSource = (data) => {
const output = {}
output.text = data.text
output.spoiler_text = data.spoiler_text
output.content_type = data.content_type
return output
}
export const parseStatus = (data) => {
const output = {}
2022-07-31 11:36:02 +02:00
const masto = Object.prototype.hasOwnProperty.call(data, 'account')
if (masto) {
output.favorited = data.favourited
output.fave_num = data.favourites_count
output.repeated = data.reblogged
output.repeat_num = data.reblogs_count
output.bookmarked = data.bookmarked
output.type = data.reblog ? 'retweet' : 'status'
output.nsfw = data.sensitive
output.raw_html = data.content
output.emojis = data.emojis
2019-01-14 20:58:23 +01:00
output.tags = data.tags
output.edited_at = data.edited_at
2022-06-08 05:31:48 +02:00
if (data.pleroma) {
const { pleroma } = data
output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text
output.statusnet_conversation_id = data.pleroma.conversation_id
2019-04-10 14:02:14 +02:00
output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
2019-09-14 04:59:24 +02:00
output.thread_muted = pleroma.thread_muted
2020-01-28 16:09:25 +01:00
output.emoji_reactions = pleroma.emoji_reactions
output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible
2023-07-13 02:45:44 +02:00
output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined
2023-07-13 06:37:57 +02:00
output.quote_id = pleroma.quote_id ? pleroma.quote_id : (output.quote ? output.quote.id : undefined)
2023-07-13 02:45:44 +02:00
output.quote_url = pleroma.quote_url
output.quote_visible = pleroma.quote_visible
} else {
output.text = data.content
output.summary = data.spoiler_text
}
output.in_reply_to_status_id = data.in_reply_to_id
2019-01-17 21:22:51 +01:00
output.in_reply_to_user_id = data.in_reply_to_account_id
2019-03-28 08:46:41 +01:00
output.replies_count = data.replies_count
2019-01-21 19:41:17 +01:00
if (output.type === 'retweet') {
output.retweeted_status = parseStatus(data.reblog)
}
2019-01-17 21:22:51 +01:00
output.summary_raw_html = escape(data.spoiler_text)
2019-01-17 21:44:31 +01:00
output.external_url = data.url
2019-06-18 22:28:31 +02:00
output.poll = data.poll
if (output.poll) {
output.poll.options = (output.poll.options || []).map(field => ({
...field,
2021-08-13 11:19:57 +02:00
title_html: escape(field.title)
}))
}
2019-04-04 18:47:25 +02:00
output.pinned = data.pinned
output.muted = data.muted
} else {
output.favorited = data.favorited
output.fave_num = data.fave_num
output.repeated = data.repeated
output.repeat_num = data.repeat_num
// catchall, temporary
// Object.assign(output, data)
output.type = qvitterStatusType(data)
if (data.nsfw === undefined) {
output.nsfw = isNsfw(data)
if (data.retweeted_status) {
output.nsfw = data.retweeted_status.nsfw
}
} else {
output.nsfw = data.nsfw
}
2021-06-12 18:54:03 +02:00
output.raw_html = data.statusnet_html
output.text = data.text
2019-01-17 21:22:51 +01:00
output.in_reply_to_status_id = data.in_reply_to_status_id
output.in_reply_to_user_id = data.in_reply_to_user_id
2019-01-21 19:41:17 +01:00
output.in_reply_to_screen_name = data.in_reply_to_screen_name
2019-01-14 20:58:23 +01:00
output.statusnet_conversation_id = data.statusnet_conversation_id
if (output.type === 'retweet') {
output.retweeted_status = parseStatus(data.retweeted_status)
}
2019-01-17 21:22:51 +01:00
output.summary = data.summary
output.summary_html = data.summary_html
2019-01-17 21:44:31 +01:00
output.external_url = data.external_url
output.is_local = data.is_local
}
output.id = String(data.id)
output.visibility = data.visibility
output.card = data.card
output.created_at = new Date(data.created_at)
// Converting to string, the right way.
output.in_reply_to_status_id = output.in_reply_to_status_id
? String(output.in_reply_to_status_id)
: null
output.in_reply_to_user_id = output.in_reply_to_user_id
? String(output.in_reply_to_user_id)
: null
output.user = parseUser(masto ? data.account : data.user)
output.attentions = ((masto ? data.mentions : data.attentions) || []).map(parseUser)
output.attachments = ((masto ? data.media_attachments : data.attachments) || [])
.map(parseAttachment)
const retweetedStatus = masto ? data.reblog : data.retweeted_status
if (retweetedStatus) {
output.retweeted_status = parseStatus(retweetedStatus)
}
output.favoritedBy = []
output.rebloggedBy = []
2022-08-02 02:45:52 +02:00
if (Object.prototype.hasOwnProperty.call(data, 'originalStatus')) {
Object.assign(output, data.originalStatus)
}
return output
}
export const parseNotification = (data) => {
const mastoDict = {
2022-07-31 11:35:48 +02:00
favourite: 'like',
reblog: 'repeat'
}
2022-07-31 11:36:02 +02:00
const masto = !Object.prototype.hasOwnProperty.call(data, 'ntype')
const output = {}
2020-04-23 13:27:27 +02:00
if (masto) {
output.type = mastoDict[data.type] || data.type
2019-03-18 17:30:34 +01:00
output.seen = data.pleroma.is_seen
output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
2019-03-31 20:50:34 +02:00
output.action = output.status // TODO: Refactor, this is unneeded
2019-12-10 15:54:28 +01:00
output.target = output.type !== 'move'
? null
: parseUser(data.target)
output.from_profile = parseUser(data.account)
output.emoji = data.emoji
2022-12-18 20:04:58 +01:00
output.emoji_url = data.emoji_url
2020-11-15 12:57:02 +01:00
if (data.report) {
output.report = data.report
output.report.content = data.report.content
2020-12-04 11:48:15 +01:00
output.report.acct = parseUser(data.report.account)
2020-11-15 12:57:02 +01:00
output.report.actor = parseUser(data.report.actor)
output.report.statuses = data.report.statuses.map(parseStatus)
}
} else {
const parsedNotice = parseStatus(data.notice)
output.type = data.ntype
output.seen = Boolean(data.is_seen)
output.status = output.type === 'like'
? parseStatus(data.notice.favorited_status)
: parsedNotice
output.action = parsedNotice
2020-05-07 15:10:53 +02:00
output.from_profile = output.type === 'pleroma:chat_mention' ? parseUser(data.account) : parseUser(data.from_profile)
}
output.created_at = new Date(data.created_at)
output.id = parseInt(data.id)
return output
}
const isNsfw = (status) => {
const nsfwRegex = /#nsfw/i
return (status.tags || []).includes('nsfw') || !!(status.text || '').match(nsfwRegex)
}
export const parseLinkHeaderPagination = (linkHeader, opts = {}) => {
const flakeId = opts.flakeId
const parsedLinkHeader = parseLinkHeader(linkHeader)
if (!parsedLinkHeader) return
const maxId = parsedLinkHeader.next.max_id
const minId = parsedLinkHeader.prev.min_id
return {
maxId: flakeId ? maxId : parseInt(maxId, 10),
minId: flakeId ? minId : parseInt(minId, 10)
}
}
2020-05-07 15:10:53 +02:00
export const parseChat = (chat) => {
const output = {}
output.id = chat.id
output.account = parseUser(chat.account)
output.unread = chat.unread
output.lastMessage = parseChatMessage(chat.last_message)
output.updated_at = new Date(chat.updated_at)
return output
}
export const parseChatMessage = (message) => {
if (!message) { return }
if (message.isNormalized) { return message }
const output = message
output.id = message.id
output.created_at = new Date(message.created_at)
output.chat_id = message.chat_id
output.emojis = message.emojis
2021-06-12 18:54:03 +02:00
output.content = message.content
2020-05-07 15:10:53 +02:00
if (message.attachment) {
output.attachments = [parseAttachment(message.attachment)]
} else {
output.attachments = []
}
2020-10-29 11:33:06 +01:00
output.pending = !!message.pending
output.error = false
output.idempotency_key = message.idempotency_key
2020-05-07 15:10:53 +02:00
output.isNormalized = true
return output
}