From 8563c974037dc7346e9cd67bc085f9b9c8be6104 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Tue, 9 Apr 2024 13:54:10 -1000 Subject: [PATCH] Fix conversion between database and Lysand types --- build.ts | 2 +- database/entities/Attachment.ts | 23 ++++++++++ database/entities/Emoji.ts | 37 ++++------------ database/entities/Like.ts | 9 ++-- database/entities/Status.ts | 78 +++++++-------------------------- database/entities/User.ts | 77 +++++++++++++++----------------- packages/lysand-types/index.ts | 28 +++++++++++- 7 files changed, 118 insertions(+), 136 deletions(-) diff --git a/build.ts b/build.ts index 05910315..1139ba1f 100644 --- a/build.ts +++ b/build.ts @@ -36,7 +36,7 @@ await $`sed -i 's|import("node_modules/|import("./node_modules/|g' dist/*.js`; // Copy generated Prisma client to dist await $`mkdir -p dist/node_modules/@prisma`; await $`cp -r ${process.cwd()}/node_modules/@prisma dist/node_modules/`; -await $`cp -r ${process.cwd()}/node_modules/.prisma dist/node_modules`; +//await $`cp -r ${process.cwd()}/node_modules/.prisma dist/node_modules`; await $`mkdir -p dist/node_modules/.bin`; await $`cp -r ${process.cwd()}/node_modules/.bin/prisma dist/node_modules/.bin`; await $`cp -r ${process.cwd()}/node_modules/prisma dist/node_modules/`; diff --git a/database/entities/Attachment.ts b/database/entities/Attachment.ts index e5749359..8c16acf5 100644 --- a/database/entities/Attachment.ts +++ b/database/entities/Attachment.ts @@ -3,6 +3,7 @@ import type { Config } from "config-manager"; import { MediaBackendType } from "media-manager"; import type { APIAsyncAttachment } from "~types/entities/async_attachment"; import type { APIAttachment } from "~types/entities/attachment"; +import type * as Lysand from "lysand-types"; export const attachmentToAPI = ( attachment: Attachment, @@ -57,6 +58,28 @@ export const attachmentToAPI = ( }; }; +export const attachmentToLysand = ( + attachment: Attachment, +): Lysand.ContentFormat => { + return { + [attachment.mime_type]: { + content: attachment.url, + blurhash: attachment.blurhash ?? undefined, + description: attachment.description ?? undefined, + duration: attachment.duration ?? undefined, + fps: attachment.fps ?? undefined, + height: attachment.height ?? undefined, + size: attachment.size ?? undefined, + hash: attachment.sha256 + ? { + sha256: attachment.sha256, + } + : undefined, + width: attachment.width ?? undefined, + }, + }; +}; + export const getUrl = (name: string, config: Config) => { if (config.media.backend === MediaBackendType.LOCAL) { return new URL(`/media/${name}`, config.http.base_url).toString(); diff --git a/database/entities/Emoji.ts b/database/entities/Emoji.ts index 6f9f57f1..f2b5d3b7 100644 --- a/database/entities/Emoji.ts +++ b/database/entities/Emoji.ts @@ -1,7 +1,7 @@ import type { Emoji } from "@prisma/client"; import { client } from "~database/datasource"; import type { APIEmoji } from "~types/entities/emoji"; -import type { Emoji as LysandEmoji } from "~types/lysand/extensions/org.lysand/custom_emojis"; +import type * as Lysand from "lysand-types"; /** * Represents an emoji entity in the database. @@ -29,7 +29,7 @@ export const parseEmojis = async (text: string): Promise => { }); }; -export const addEmojiIfNotExists = async (emoji: LysandEmoji) => { +export const addEmojiIfNotExists = async (emoji: Lysand.Emoji) => { const existingEmoji = await client.emoji.findFirst({ where: { shortcode: emoji.name, @@ -43,8 +43,8 @@ export const addEmojiIfNotExists = async (emoji: LysandEmoji) => { data: { shortcode: emoji.name, url: emoji.url[0].content, - alt: emoji.alt || null, - content_type: emoji.url[0].content_type, + alt: emoji.alt || emoji.url[0].description || undefined, + content_type: Object.keys(emoji.url)[0], visible_in_picker: true, }, }); @@ -64,34 +64,15 @@ export const emojiToAPI = (emoji: Emoji): APIEmoji => { }; }; -export const emojiToLysand = (emoji: Emoji): LysandEmoji => { +export const emojiToLysand = (emoji: Emoji): Lysand.Emoji => { return { name: emoji.shortcode, - url: [ - { + url: { + [emoji.content_type]: { content: emoji.url, - content_type: emoji.content_type, + description: emoji.alt || undefined, }, - ], + }, alt: emoji.alt || undefined, }; }; - -/** - * Converts the emoji to an ActivityPub object. - * @returns The ActivityPub object. - */ -export const emojiToActivityPub = (emoji: Emoji): object => { - // replace any with your ActivityPub Emoji type - return { - type: "Emoji", - name: `:${emoji.shortcode}:`, - updated: new Date().toISOString(), - icon: { - type: "Image", - url: emoji.url, - mediaType: emoji.content_type, - alt: emoji.alt || undefined, - }, - }; -}; diff --git a/database/entities/Like.ts b/database/entities/Like.ts index 675c2335..dcaf092d 100644 --- a/database/entities/Like.ts +++ b/database/entities/Like.ts @@ -1,14 +1,14 @@ import type { Like } from "@prisma/client"; import { config } from "config-manager"; import { client } from "~database/datasource"; -import type { Like as LysandLike } from "~types/lysand/Object"; import type { StatusWithRelations } from "./Status"; import type { UserWithRelations } from "./User"; +import type * as Lysand from "lysand-types"; /** * Represents a Like entity in the database. */ -export const toLysand = (like: Like): LysandLike => { +export const toLysand = (like: Like): Lysand.Like => { return { id: like.id, // biome-ignore lint/suspicious/noExplicitAny: to be rewritten @@ -17,7 +17,10 @@ export const toLysand = (like: Like): LysandLike => { created_at: new Date(like.createdAt).toISOString(), // biome-ignore lint/suspicious/noExplicitAny: to be rewritten object: (like as any).liked?.uri, - uri: new URL(`/actions/${like.id}`, config.http.base_url).toString(), + uri: new URL( + `/objects/like/${like.id}`, + config.http.base_url, + ).toString(), }; }; diff --git a/database/entities/Status.ts b/database/entities/Status.ts index 07bdb7a9..293cd2d2 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -18,8 +18,9 @@ import { client } from "~database/datasource"; import type { APIAttachment } from "~types/entities/attachment"; import type { APIStatus } from "~types/entities/status"; import type { LysandPublication, Note } from "~types/lysand/Object"; +import type * as Lysand from "lysand-types"; import { applicationToAPI } from "./Application"; -import { attachmentToAPI } from "./Attachment"; +import { attachmentToAPI, attachmentToLysand } from "./Attachment"; import { emojiToAPI, emojiToLysand, parseEmojis } from "./Emoji"; import type { UserWithRelations } from "./User"; import { fetchRemoteUser, parseMentionsUris, userToAPI } from "./User"; @@ -533,78 +534,33 @@ export const statusToAPI = async ( }; }; -/* export const statusToActivityPub = async ( - status: StatusWithRelations - // user?: UserWithRelations -): Promise => { - // replace any with your ActivityPub type - return { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://mastodon.social/schemas/litepub-0.1.jsonld", - ], - id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}`, - type: "Note", - summary: status.spoilerText, - content: status.content, - published: new Date(status.createdAt).toISOString(), - url: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}`, - attributedTo: `${config.http.base_url}/users/${status.authorId}`, - to: ["https://www.w3.org/ns/activitystreams#Public"], - cc: [], // add recipients here - sensitive: status.sensitive, - attachment: (status.attachments ?? []).map( - a => attachmentToActivityPub(a) as ActivityPubAttachment // replace with your function - ), - tag: [], // add tags here - replies: { - id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}/replies`, - type: "Collection", - totalItems: status._count.replies, - }, - likes: { - id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}/likes`, - type: "Collection", - totalItems: status._count.likes, - }, - shares: { - id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}/shares`, - type: "Collection", - totalItems: status._count.reblogs, - }, - inReplyTo: status.inReplyToPostId - ? `${config.http.base_url}/users/${status.inReplyToPost?.authorId}/statuses/${status.inReplyToPostId}` - : null, - visibility: "public", // adjust as needed - // add more fields as needed - }; -}; */ - -export const statusToLysand = (status: StatusWithRelations): Note => { +export const statusToLysand = (status: StatusWithRelations): Lysand.Note => { return { type: "Note", created_at: new Date(status.createdAt).toISOString(), id: status.id, author: status.authorId, - uri: new URL(`/statuses/${status.id}`, config.http.base_url).toString(), - contents: [ - { + uri: new URL( + `/objects/note/${status.id}`, + config.http.base_url, + ).toString(), + content: { + "text/html": { content: status.content, - content_type: "text/html", }, - { - // Content converted to plaintext + "text/plain": { content: htmlToText(status.content), - content_type: "text/plain", }, - ], - // TODO: Add attachments - attachments: [], + }, + attachments: status.attachments.map((attachment) => + attachmentToLysand(attachment), + ), is_sensitive: status.sensitive, mentions: status.mentions.map((mention) => mention.uri || ""), - quotes: status.quotingPost ? [status.quotingPost.uri || ""] : [], - replies_to: status.inReplyToPostId ? [status.inReplyToPostId] : [], + quotes: status.quotingPost?.uri ?? undefined, + replies_to: status.inReplyToPost?.uri ?? undefined, subject: status.spoilerText, + visibility: status.visibility as Lysand.Visibility, extensions: { "org.lysand:custom_emojis": { emojis: status.emojis.map((emoji) => emojiToLysand(emoji)), diff --git a/database/entities/User.ts b/database/entities/User.ts index a70155f7..48ab3a62 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -6,11 +6,10 @@ import { htmlToText } from "html-to-text"; import { client } from "~database/datasource"; import type { APIAccount } from "~types/entities/account"; import type { APISource } from "~types/entities/source"; -import type { LysandUser } from "~types/lysand/Object"; +import type * as Lysand from "lysand-types"; import { addEmojiIfNotExists, emojiToAPI, emojiToLysand } from "./Emoji"; import { addInstanceIfNotExists } from "./Instance"; import { userRelations } from "./relations"; -import { getUrl } from "./Attachment"; import { createNewRelationship } from "./Relationship"; export interface AuthData { @@ -147,7 +146,7 @@ export const fetchRemoteUser = async (uri: string) => { }, }); - const data = (await response.json()) as Partial; + const data = (await response.json()) as Partial; if ( !( @@ -155,9 +154,9 @@ export const fetchRemoteUser = async (uri: string) => { data.username && data.uri && data.created_at && - data.disliked && + data.dislikes && data.featured && - data.liked && + data.likes && data.followers && data.following && data.inbox && @@ -178,9 +177,9 @@ export const fetchRemoteUser = async (uri: string) => { uri: data.uri, createdAt: new Date(data.created_at), endpoints: { - disliked: data.disliked, + dislikes: data.dislikes, featured: data.featured, - liked: data.liked, + likes: data.likes, followers: data.followers, following: data.following, inbox: data.inbox, @@ -444,7 +443,7 @@ export const userToAPI = ( /** * Should only return local users */ -export const userToLysand = (user: UserWithRelations): LysandUser => { +export const userToLysand = (user: UserWithRelations): Lysand.User => { if (user.instanceId !== null) { throw new Error("Cannot convert remote user to Lysand format"); } @@ -452,29 +451,28 @@ export const userToLysand = (user: UserWithRelations): LysandUser => { return { id: user.id, type: "User", - uri: user.uri || "", - bio: [ - { + uri: + user.uri || + new URL(`/users/${user.id}`, config.http.base_url).toString(), + bio: { + "text/html": { content: user.note, - content_type: "text/html", }, - { + "text/plain": { content: htmlToText(user.note), - content_type: "text/plain", }, - ], + }, created_at: new Date(user.createdAt).toISOString(), - - disliked: new URL( - `/users/${user.id}/disliked`, + dislikes: new URL( + `/users/${user.id}/dislikes`, config.http.base_url, ).toString(), featured: new URL( `/users/${user.id}/featured`, config.http.base_url, ).toString(), - liked: new URL( - `/users/${user.id}/liked`, + likes: new URL( + `/users/${user.id}/likes`, config.http.base_url, ).toString(), followers: new URL( @@ -495,40 +493,35 @@ export const userToLysand = (user: UserWithRelations): LysandUser => { ).toString(), indexable: false, username: user.username, - avatar: [ - { - content: getAvatarUrl(user, config) || "", - content_type: `image/${user.avatar.split(".")[1]}`, + avatar: { + [user.avatar.split(".")[1]]: { + content: getAvatarUrl(user, config), }, - ], - header: [ - { - content: getHeaderUrl(user, config) || "", - content_type: `image/${user.header.split(".")[1]}`, + }, + header: { + [user.header.split(".")[1]]: { + content: getHeaderUrl(user, config), }, - ], + }, display_name: user.displayName, + fields: (user.source as APISource).fields.map((field) => ({ - key: [ - { + key: { + "text/html": { content: field.name, - content_type: "text/html", }, - { + "text/plain": { content: htmlToText(field.name), - content_type: "text/plain", }, - ], - value: [ - { + }, + value: { + "text/html": { content: field.value, - content_type: "text/html", }, - { + "text/plain": { content: htmlToText(field.value), - content_type: "text/plain", }, - ], + }, })), public_key: { actor: new URL( diff --git a/packages/lysand-types/index.ts b/packages/lysand-types/index.ts index ef6671ef..9ec4d0ed 100644 --- a/packages/lysand-types/index.ts +++ b/packages/lysand-types/index.ts @@ -44,9 +44,19 @@ export interface Entity { created_at: string; uri: string; type: string; + extensions?: { + "org.lysand:custom_emojis"?: { + emojis: Emoji[]; + }; + [key: string]: object | undefined; + }; } -export interface Publication { +export interface InlineCustomEmojis { + [key: string]: Emoji; +} + +export interface Publication extends Entity { type: "Note" | "Patch"; author: string; content?: ContentFormat; @@ -57,6 +67,19 @@ export interface Publication { subject?: string; is_sensitive?: boolean; visibility: Visibility; + extensions?: Entity["extensions"] & { + "org.lysand:reactions"?: { + reactions: string; + }; + "org.lysand:polls"?: { + poll: { + options: ContentFormat[]; + votes: number[]; + multiple_choice?: boolean; + expires_at: string; + }; + }; + }; } export enum Visibility { @@ -96,6 +119,9 @@ export interface User extends Entity { dislikes: string; inbox: string; outbox: string; + extensions?: Entity["extensions"] & { + "org.lysand:vanity"?: VanityExtension; + }; } export interface Field {