From e05dca9fc1176f1e59075f8be0669055b01e279c Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Sun, 3 Mar 2024 14:45:21 -1000 Subject: [PATCH] ActivityPub work --- database/entities/Emoji.ts | 41 +++++++++++ database/entities/Status.ts | 47 ++++++++++++ types/activitypub.ts | 141 ++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 types/activitypub.ts diff --git a/database/entities/Emoji.ts b/database/entities/Emoji.ts index 55511503..e72af19c 100644 --- a/database/entities/Emoji.ts +++ b/database/entities/Emoji.ts @@ -76,3 +76,44 @@ export const emojiToLysand = (emoji: Emoji): LysandEmoji => { alt: emoji.alt || undefined, }; }; + +/** + * Converts the emoji to an ActivityPub object. + * @returns The ActivityPub object. + */ +export const emojiToActivityPub = (emoji: Emoji): any => { + // 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, + }, + }; +}; + +export const addAPEmojiIfNotExists = async (apEmoji: any) => { + // replace any with your ActivityPub Emoji type + const existingEmoji = await client.emoji.findFirst({ + where: { + shortcode: apEmoji.name.replace(/:/g, ""), + instance: null, + }, + }); + + if (existingEmoji) return existingEmoji; + + return await client.emoji.create({ + data: { + shortcode: apEmoji.name.replace(/:/g, ""), + url: apEmoji.icon.url, + alt: apEmoji.icon.alt || null, + content_type: apEmoji.icon.mediaType, + visible_in_picker: true, + }, + }); +}; diff --git a/database/entities/Status.ts b/database/entities/Status.ts index 25b36b7e..16849dc3 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -612,6 +612,53 @@ 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 => { return { type: "Note", diff --git a/types/activitypub.ts b/types/activitypub.ts new file mode 100644 index 00000000..619b8563 --- /dev/null +++ b/types/activitypub.ts @@ -0,0 +1,141 @@ +export type APActivityPubContext = + | "https://www.w3.org/ns/activitystreams" + | { + ostatus: string; + atomUri: string; + inReplyToAtomUri: string; + conversation: string; + sensitive: string; + toot: string; + votersCount: string; + litepub: string; + directMessage: string; + }; + +export interface APActivityPubObject { + id: string; + type: string; + summary?: string; + inReplyTo?: string; + published: string; + url: string; + attributedTo: string; + to: string[]; + cc: string[]; + sensitive?: boolean; + atomUri: string; + inReplyToAtomUri?: string; + conversation: string; + content: string; + contentMap: Record; + attachment: APActivityPubAttachment[]; + tag: APTag[]; + context?: string; + quoteUri?: string; + quoteUrl?: string; + source?: { + content: string; + mediaType: string; + }; +} + +export interface APActivityPubAttachment { + type?: string; + mediaType?: string; + url?: string; + name?: string; +} + +export interface APActivityPubCollection { + id: string; + type: string; + first?: { + type: string; + next: string; + partOf: string; + items: any[]; // replace any with your item type + }; +} + +export interface APActivityPubNote extends APActivityPubObject { + type: "Note"; +} + +export interface APActivityPubActivity { + "@context": APActivityPubContext[]; + id: string; + type: string; + actor: string; + published: string; + to: string[]; + cc: string[]; + object: APActivityPubNote; +} + +export type APActorContext = + | "https://www.w3.org/ns/activitystreams" + | "https://w3id.org/security/v1" + | Record< + string, + | string + | { "@id": string; "@type": string } + | { "@container": string; "@id": string } + >; + +export interface APActorPublicKey { + id: string; + owner: string; + publicKeyPem: string; +} + +export interface APActorEndpoints { + sharedInbox: string; +} + +export interface APActorIcon { + type: string; + mediaType: string; + url: string; +} + +export interface APActor { + "@context": APActorContext[]; + id: string; + type: string; + following: string; + followers: string; + inbox: string; + outbox: string; + featured: string; + featuredTags: string; + preferredUsername: string; + name: string; + summary: string; + url: string; + manuallyApprovesFollowers: boolean; + discoverable: boolean; + indexable: boolean; + published: string; + memorial: boolean; + devices: string; + publicKey: APActorPublicKey; + tag: APTag[]; + attachment: APAttachment[]; + endpoints: APActorEndpoints; + icon: APActorIcon; +} + +export interface APTag { + type: string; + href: string; + name: string; +} + +export interface APAttachment { + type: string; + mediaType: string; + url: string; + name?: string; + blurhash?: string; + description?: string; +}