From 57d58e4bd9df88ce818a48a8a90efa80b7479f13 Mon Sep 17 00:00:00 2001 From: Mags <4146037+MagsMagnoli@users.noreply.github.com> Date: Tue, 26 Nov 2024 07:14:11 -0500 Subject: [PATCH] feat(bluesky): add support for video --- .../__tests__/create-media-record.spec.ts | 45 +++++++++++++++++++ src/helpers/bluesky/create-media-record.ts | 37 +++++++++++++++ src/helpers/medias/parse-blob-for-bluesky.ts | 1 + .../__tests__/bluesky-sender.service.spec.ts | 30 +++++++++++-- src/services/bluesky-sender.service.ts | 14 ++---- 5 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 src/helpers/bluesky/__tests__/create-media-record.spec.ts create mode 100644 src/helpers/bluesky/create-media-record.ts diff --git a/src/helpers/bluesky/__tests__/create-media-record.spec.ts b/src/helpers/bluesky/__tests__/create-media-record.spec.ts new file mode 100644 index 0000000..570c35f --- /dev/null +++ b/src/helpers/bluesky/__tests__/create-media-record.spec.ts @@ -0,0 +1,45 @@ +import { createMediaRecord } from "../create-media-record"; + +describe("createMediaRecord", () => { + const mockImageAttachments = [ + { alt_text: "Alt text 1", data: { blob: { original: "image1.jpg" } } }, + { alt_text: "Alt text 2", data: { blob: { original: "image2.jpg" } } }, + ]; + + const mockVideoAttachment = [ + { alt_text: null, data: { blob: { original: "video.mp4" } } }, + ]; + + it("should an image media record for 'image' mediaType", () => { + const result = createMediaRecord("image", mockImageAttachments); + expect(result).toEqual({ + media: { + $type: "app.bsky.embed.images", + images: [ + { alt: "Alt text 1", image: "image1.jpg" }, + { alt: "Alt text 2", image: "image2.jpg" }, + ], + }, + }); + }); + + it("should a video media record for 'video' mediaType", () => { + const result = createMediaRecord("video", mockVideoAttachment); + expect(result).toEqual({ + media: { + $type: "app.bsky.embed.video", + video: "video.mp4", + }, + }); + }); + + it("should return an empty object for undefined mediaType", () => { + const result = createMediaRecord(undefined, []); + expect(result).toEqual({}); + }); + + it("should return an empty object for unhandled mediaType", () => { + const result = createMediaRecord("audio", []); + expect(result).toEqual({}); + }); +}); diff --git a/src/helpers/bluesky/create-media-record.ts b/src/helpers/bluesky/create-media-record.ts new file mode 100644 index 0000000..0ccd843 --- /dev/null +++ b/src/helpers/bluesky/create-media-record.ts @@ -0,0 +1,37 @@ +import { BlueskyMediaAttachment } from "../../types"; + +/** + * Creates a media record based on the given media type and attachments. + * + * @param {'image' | 'video' | undefined} mediaType - The type of the media (image or video). + * @param {BlueskyMediaAttachment[]} mediaAttachments - The media attachments to include in the record. + * @returns {Object} The media record object tailored to the media type. + */ +export const createMediaRecord = ( + mediaType: "image" | "video" | undefined, + mediaAttachments: BlueskyMediaAttachment[], +) => { + switch (mediaType) { + case "image": + return { + media: { + $type: "app.bsky.embed.images", + images: mediaAttachments.map((i) => ({ + alt: i.alt_text ?? "", + image: i.data.blob.original, + })), + }, + }; + + case "video": + return { + media: { + $type: "app.bsky.embed.video", + video: mediaAttachments[0].data.blob.original, + }, + }; + + default: + return {}; + } +}; diff --git a/src/helpers/medias/parse-blob-for-bluesky.ts b/src/helpers/medias/parse-blob-for-bluesky.ts index 0c1c249..7c7fc7f 100644 --- a/src/helpers/medias/parse-blob-for-bluesky.ts +++ b/src/helpers/medias/parse-blob-for-bluesky.ts @@ -25,6 +25,7 @@ export const parseBlobForBluesky = async ( "image/jpg", "image/jpeg", "image/webp", + "video/mp4", ]; const mimeType = blob.type; diff --git a/src/services/__tests__/bluesky-sender.service.spec.ts b/src/services/__tests__/bluesky-sender.service.spec.ts index dbd751b..6a7f136 100644 --- a/src/services/__tests__/bluesky-sender.service.spec.ts +++ b/src/services/__tests__/bluesky-sender.service.spec.ts @@ -74,6 +74,13 @@ const embedMedia = { }, }; +const embedVideo = { + $type: "blob", + mimeType: "video/mp4", + ref: "blobRef", + size: "1024", +}; + describe("blueskySenderService", () => { beforeEach(() => { postSpy.mockClear(); @@ -141,14 +148,26 @@ describe("blueskySenderService", () => { }); }); - describe("when the tweet as a video", () => { + describe("when the tweet is a video", () => { beforeAll(() => { mediaDownloaderServiceMock.mockResolvedValue( makeBlobFromFile("video-mp4.mp4", "video/mp4"), ); + uploadBlobSpy.mockResolvedValue({ + data: { + blob: { + original: { + $type: "blob", + ref: "blobRef", + mimeType: "video/mp4", + size: "1024", + }, + }, + }, + }); }); - it("should send the post without media ", async () => { + it("should send the post with media ", async () => { const mediaVideo: Media = { type: "video", id: "id", @@ -157,14 +176,17 @@ describe("blueskySenderService", () => { }; await blueskySenderService(client, post, [mediaVideo], log); - expect(uploadBlobSpy).toHaveBeenCalledTimes(0); + expect(uploadBlobSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledTimes(1); expect(postSpy).toHaveBeenCalledWith({ $type: "app.bsky.feed.post", createdAt: new Date(post.tweet.timestamp!).toISOString(), text: "Tweet text", facets: undefined, - embed: undefined, + embed: { + $type: "app.bsky.embed.video", + video: embedVideo, + }, }); }); }); diff --git a/src/services/bluesky-sender.service.ts b/src/services/bluesky-sender.service.ts index ccfeea3..30a4481 100644 --- a/src/services/bluesky-sender.service.ts +++ b/src/services/bluesky-sender.service.ts @@ -6,6 +6,7 @@ import { buildReplyEntry, getBlueskyChunkLinkMetadata, } from "../helpers/bluesky"; +import { createMediaRecord } from "../helpers/bluesky/create-media-record"; import { savePostToCache } from "../helpers/cache/save-post-to-cache"; import { oraProgress } from "../helpers/logs"; import { parseBlobForBluesky } from "../helpers/medias/parse-blob-for-bluesky"; @@ -147,17 +148,8 @@ export const blueskySenderService = async ( } : {}; - const mediaRecord = mediaAttachments.length - ? { - media: { - $type: "app.bsky.embed.images", - images: mediaAttachments.map((i) => ({ - alt: i.alt_text ?? "", - image: i.data.blob.original, - })), - }, - } - : {}; + const mediaType = medias[0]?.type; + const mediaRecord = createMediaRecord(mediaType, mediaAttachments); const card = await getBlueskyChunkLinkMetadata(richText, client); const externalRecord = card