Implement federation of statuses

This commit is contained in:
Jesse Wierzbinski 2024-04-09 16:05:02 -10:00
parent 8563c97403
commit a58c81c8e9
No known key found for this signature in database
11 changed files with 788 additions and 411 deletions

View file

@ -59,8 +59,7 @@ export const isViewableByUser = (status: Status, user: User | null) => {
export const fetchFromRemote = async (uri: string): Promise<Status | null> => {
// Check if already in database
const existingStatus: StatusWithRelations | null =
/* const existingStatus: StatusWithRelations | null =
await client.status.findFirst({
where: {
uri: uri,
@ -112,7 +111,7 @@ export const fetchFromRemote = async (uri: string): Promise<Status | null> => {
}
: undefined,
quote: quotingStatus || undefined,
});
}); */
};
/**
@ -192,12 +191,127 @@ export const getDescendants = async (
return viewableDescendants;
};
/**
* Get people mentioned in the content (match @username or @username@domain.com mentions)
* @param text The text to parse mentions from.
* @returns An array of users mentioned in the text.
*/
export const parseTextMentions = async (text: string) => {
const mentionedPeople =
text.match(/@[a-zA-Z0-9_]+(@[a-zA-Z0-9_]+)?/g) ?? [];
return await client.user.findMany({
where: {
OR: mentionedPeople.map((person) => ({
username: person.split("@")[1],
instance: {
base_url: person.split("@")[2],
},
})),
},
include: userRelations,
});
};
export const createNewStatus = async (
author: User,
content: Lysand.ContentFormat,
visibility: APIStatus["visibility"],
is_sensitive: boolean,
spoiler_text: string,
emojis: Emoji[],
uri?: string,
mentions?: UserWithRelations[],
/** List of IDs of database Attachment objects */
media_attachments?: string[],
inReplyTo?: StatusWithRelations,
quoting?: StatusWithRelations,
) => {
let htmlContent: string;
if (content["text/html"]) {
htmlContent = content["text/html"].content;
} else if (content["text/markdown"]) {
htmlContent = linkifyHtml(
await sanitizeHtml(await parse(content["text/markdown"].content)),
);
} else if (content["text/plain"]) {
htmlContent = linkifyStr(content["text/plain"].content);
// Split by newline and add <p> tags
htmlContent = htmlContent
.split("\n")
.map((line) => `<p>${line}</p>`)
.join("\n");
} else {
htmlContent = "";
}
// Parse emojis and fuse with existing emojis
let foundEmojis = emojis;
if (author.instanceId === null) {
const parsedEmojis = await parseEmojis(htmlContent);
// Fuse and deduplicate
foundEmojis = [...emojis, ...parsedEmojis].filter(
(emoji, index, self) =>
index === self.findIndex((t) => t.id === emoji.id),
);
}
const status = await client.status.create({
data: {
authorId: author.id,
content: htmlContent,
contentSource:
content["text/plain"]?.content ||
content["text/markdown"]?.content ||
"",
contentType: "text/html",
visibility: visibility,
sensitive: is_sensitive,
spoilerText: spoiler_text,
isReblog: false, // DEPRECATED FIELD
emojis: {
connect: foundEmojis.map((emoji) => {
return {
id: emoji.id,
};
}),
},
attachments: media_attachments
? {
connect: media_attachments.map((attachment) => {
return {
id: attachment,
};
}),
}
: undefined,
inReplyToPostId: inReplyTo?.id,
quotingPostId: quoting?.id,
instanceId: author.instanceId || undefined,
uri: uri || null,
mentions: {
connect: mentions?.map((mention) => {
return {
id: mention.id,
};
}),
},
},
include: statusAndUserRelations,
});
return status;
};
/**
* Creates a new status and saves it to the database.
* @param data The data for the new status.
* @returns A promise that resolves with the new status.
*/
export const createNewStatus = async (data: {
export const createNewStatus2 = async (data: {
account: User;
application: Application | null;
content: string;
@ -539,11 +653,18 @@ export const statusToLysand = (status: StatusWithRelations): Lysand.Note => {
type: "Note",
created_at: new Date(status.createdAt).toISOString(),
id: status.id,
author: status.authorId,
uri: new URL(
`/objects/note/${status.id}`,
config.http.base_url,
).toString(),
author:
status.author.uri ||
new URL(
`/users/${status.author.id}`,
config.http.base_url,
).toString(),
uri:
status.uri ||
new URL(
`/objects/note/${status.id}`,
config.http.base_url,
).toString(),
content: {
"text/html": {
content: status.content,

View file

@ -11,6 +11,7 @@ import { addEmojiIfNotExists, emojiToAPI, emojiToLysand } from "./Emoji";
import { addInstanceIfNotExists } from "./Instance";
import { userRelations } from "./relations";
import { createNewRelationship } from "./Relationship";
import { urlToContentFormat } from "@content_types";
export interface AuthData {
user: UserWithRelations | null;
@ -493,18 +494,9 @@ export const userToLysand = (user: UserWithRelations): Lysand.User => {
).toString(),
indexable: false,
username: user.username,
avatar: {
[user.avatar.split(".")[1]]: {
content: getAvatarUrl(user, config),
},
},
header: {
[user.header.split(".")[1]]: {
content: getHeaderUrl(user, config),
},
},
avatar: urlToContentFormat(getAvatarUrl(user, config)) ?? undefined,
header: urlToContentFormat(getHeaderUrl(user, config)) ?? undefined,
display_name: user.displayName,
fields: (user.source as APISource).fields.map((field) => ({
key: {
"text/html": {