Skip to content

Commit 03e8025

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/develop'
2 parents 74452cf + 56b3ceb commit 03e8025

4 files changed

Lines changed: 82 additions & 0 deletions

File tree

Sources/ATProtoKit/APIReference/ATProtoBlueskyAPI/PostRecord/CreatePostRecord.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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:

Sources/ATProtoKit/Models/Lexicons/app.bsky/Embed/AppBskyEmbedRecordWithMedia.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ extension AppBskyLexicon.Embed {
7171
/// A video that will be embedded.
7272
case embedVideo(AppBskyLexicon.Embed.VideoDefinition)
7373

74+
/// A gallery that will be embedded.
75+
case embedGallery(AppBskyLexicon.Embed.GalleryDefinition)
76+
7477
/// An unknown case.
7578
case unknown(String, [String: CodableValue])
7679

@@ -85,6 +88,8 @@ extension AppBskyLexicon.Embed {
8588
self = .embedVideo(try AppBskyLexicon.Embed.VideoDefinition(from: decoder))
8689
case "app.bsky.embed.external":
8790
self = .embedExternal(try AppBskyLexicon.Embed.ExternalDefinition(from: decoder))
91+
case "app.bsky.embed.gallery":
92+
self = .embedGallery(try AppBskyLexicon.Embed.GalleryDefinition(from: decoder))
8893
default:
8994
let singleValueDecodingContainer = try decoder.singleValueContainer()
9095
let dictionary = try Self.decodeDictionary(from: singleValueDecodingContainer, decoder: decoder)
@@ -103,6 +108,8 @@ extension AppBskyLexicon.Embed {
103108
try container.encode(media)
104109
case .embedVideo(let value):
105110
try container.encode(value)
111+
case .embedGallery(let value):
112+
try container.encode(value)
106113
default:
107114
break
108115
}

Sources/ATProtoKit/Models/Lexicons/app.bsky/Feed/AppBskyFeedPost.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ extension AppBskyLexicon.Feed {
181181
/// A video embed.
182182
case video(AppBskyLexicon.Embed.VideoDefinition)
183183

184+
/// A gallery embed.
185+
case gallery(AppBskyLexicon.Embed.GalleryDefinition)
186+
184187
/// An external embed.
185188
case external(AppBskyLexicon.Embed.ExternalDefinition)
186189

@@ -202,6 +205,8 @@ extension AppBskyLexicon.Feed {
202205
self = .images(try AppBskyLexicon.Embed.ImagesDefinition(from: decoder))
203206
case "app.bsky.embed.video":
204207
self = .video(try AppBskyLexicon.Embed.VideoDefinition(from: decoder))
208+
case "app.bsky.embed.gallery":
209+
self = .gallery(try AppBskyLexicon.Embed.GalleryDefinition(from: decoder))
205210
case "app.bsky.embed.external":
206211
self = .external(try AppBskyLexicon.Embed.ExternalDefinition(from: decoder))
207212
case "app.bsky.embed.record":
@@ -226,6 +231,9 @@ extension AppBskyLexicon.Feed {
226231
case .video(let value):
227232
try container.encode("app.bsky.embed.video", forKey: .type)
228233
try value.encode(to: encoder)
234+
case .gallery(let galleryValue):
235+
try container.encode("app.bsky.embed.gallery", forKey: .type)
236+
try galleryValue.encode(to: encoder)
229237
case .external(let externalValue):
230238
try container.encode("app.bsky.embed.external", forKey: .type)
231239
try externalValue.encode(to: encoder)

Sources/ATProtoKit/Utilities/AttachmentLexiconLimit.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ public enum AttachmentLexiconLimit {
2424
/// [images]: https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/embed/images.json
2525
public static let postImageEmbed = 2_000_000
2626

27+
/// The maximum size of a gallery image embed: 2,000,000 bytes.
28+
///
29+
/// Declared by `app.bsky.embed.gallery` at `defs.image.properties.image.maxSize`.
30+
///
31+
/// - SeeAlso: [`app.bsky.embed.gallery`][gallery] lexicon.
32+
///
33+
/// [gallery]: https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/embed/gallery.json
34+
public static let galleryImageEmbed = 2_000_000
35+
2736
/// The maximum size of an external link thumbnail: 1,000,000 bytes.
2837
///
2938
/// Declared by `app.bsky.embed.external` at `defs.external.properties.thumb.maxSize`.

0 commit comments

Comments
 (0)