@@ -429,6 +429,12 @@ extension ATProtoBluesky {
429429 pdsURL: sessionURL,
430430 accessToken: accessToken
431431 )
432+ case . gallery( let images) :
433+ resolvedEmbed = try await uploadGallery (
434+ images,
435+ pdsURL: sessionURL,
436+ accessToken: accessToken
437+ )
432438 case . external( let url, let title, let description, let thumbnailImageURL) :
433439 resolvedEmbed = await buildExternalEmbed (
434440 from: url,
@@ -630,6 +636,49 @@ extension ATProtoBluesky {
630636 return . images( AppBskyLexicon . Embed. ImagesDefinition ( images: embedImages) )
631637 }
632638
639+ /// Uploads images to the AT Protocol as a gallery for attaching to a record at a later request.
640+ ///
641+ /// `app.bsky.embed.gallery` is the successor to `app.bsky.embed.images`, supporting a larger
642+ /// number of images per post.
643+ ///
644+ /// - Parameters:
645+ /// - images: The ``ATProtoTools/ImageQuery`` that contains the image data. The schema-level
646+ /// maximum is 20 images; clients should enforce a soft limit of 10 items in authoring UIs.
647+ /// - pdsURL: The URL of the Personal Data Server (PDS). Defaults to `https://bsky.social`.
648+ /// - accessToken: The access token used to authenticate to the user.
649+ /// - Returns: An ``AppBskyLexicon/Feed/PostRecord/EmbedUnion``, which contains an
650+ /// ``AppBskyLexicon/Embed/GalleryDefinition`` for use in a record.
651+ ///
652+ /// - Important: Each image can only be 2 MB in size.
653+ public func uploadGallery( _ images: [ ATProtoTools . ImageQuery ] , pdsURL: String = " https://bsky.social " ,
654+ accessToken: String ) async throws -> AppBskyLexicon . Feed . PostRecord . EmbedUnion {
655+ var items = [ AppBskyLexicon . Embed. GalleryDefinition. ItemUnion] ( )
656+
657+ for image in images {
658+ // Check if the image is too large.
659+ guard image. imageData. count <= AttachmentLexiconLimit . galleryImageEmbed else {
660+ throw ATBlueskyError . imageTooLarge
661+ }
662+
663+ // Upload the image, then get the server response.
664+ let blobReference = try await ATProtoKit ( canUseBlueskyRecords: false ) . uploadBlob (
665+ pdsURL: pdsURL,
666+ accessToken: accessToken,
667+ filename: image. fileName,
668+ imageData: image. imageData
669+ )
670+
671+ let galleryImage = AppBskyLexicon . Embed. GalleryDefinition. Image (
672+ imageBlob: blobReference,
673+ altText: image. altText ?? " " ,
674+ aspectRatio: image. aspectRatio
675+ )
676+ items. append ( . itemImage( galleryImage) )
677+ }
678+
679+ return . gallery( AppBskyLexicon . Embed. GalleryDefinition ( items: items) )
680+ }
681+
633682 /// A structure that contains closed captioning information.
634683 ///
635684 /// The caption file should be in an .vtt format.
@@ -910,6 +959,15 @@ extension ATProtoBluesky {
910959 /// - aspectRatio: The aspect ratio of the video. Optional.
911960 case video( video: Data , captions: [ Caption ] ? = nil , altText: String ? = nil , aspectoRatio: AppBskyLexicon . Embed . AspectRatioDefinition ? = nil )
912961
962+ /// Represents a set of images to be embedded as a gallery in the post.
963+ ///
964+ /// `app.bsky.embed.gallery` is the successor to `images`, supporting a larger number of
965+ /// images per post.
966+ ///
967+ /// - Parameter images: An array of ``ATProtoTools/ImageQuery`` objects, each containing
968+ /// the image data, metadata, and filenames of the image.
969+ case gallery( images: [ ATProtoTools . ImageQuery ] )
970+
913971 /// Represents an external link to be embedded in the post.
914972 ///
915973 /// - Parameters:
0 commit comments