Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SessionConfiguration Rework #110

Draft
wants to merge 43 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
41bded6
Resolve merge conflict
MasterJ93 Feb 2, 2025
4c75918
Remove SessionConfiguration conformance
MasterJ93 Feb 2, 2025
ad934a8
Begin to merge UserSession properties to SessionConfiguration
MasterJ93 Feb 2, 2025
6af8572
Add methods inside SessionConfiguration
MasterJ93 Feb 2, 2025
662d404
Add SessionConfigurationTools
MasterJ93 Feb 2, 2025
0318abb
Fix errors in SessionConfigurationTools
MasterJ93 Feb 2, 2025
8d4790d
Fix warnings in SessionConfigurationTools
MasterJ93 Feb 2, 2025
51da06d
Correct method order
MasterJ93 Feb 2, 2025
5735760
Set protocol properties to "get set"
MasterJ93 Feb 2, 2025
a97ce25
Remove "throws" from getValidRefreshToken()
MasterJ93 Feb 2, 2025
686e9fe
Make refreshToken optional
MasterJ93 Feb 2, 2025
355a455
Make return statement in getValidRefreshToken() optional
MasterJ93 Feb 2, 2025
c6d3882
Remove UserSession from SessionConfiguration
MasterJ93 Feb 2, 2025
bed59b5
Make refreshToken optional
MasterJ93 Feb 2, 2025
bd14d65
Add SessionConfiguration extension
MasterJ93 Feb 2, 2025
9b93132
Merge branch 'develop'
MasterJ93 Feb 3, 2025
e5de4d2
Merge branch 'develop'
MasterJ93 Feb 12, 2025
236ded6
Merge branch 'main'
MasterJ93 Feb 20, 2025
77fa814
Merge branch 'develop'
MasterJ93 Feb 23, 2025
2097c4d
Merge branch 'develop'
MasterJ93 Feb 25, 2025
ba38425
Merge branch 'develop'
MasterJ93 Feb 25, 2025
1fd69f6
Update method signatures
MasterJ93 Feb 25, 2025
39d8304
Mark properties as get-only
MasterJ93 Feb 25, 2025
8a981f1
Remove Sendable
MasterJ93 Feb 25, 2025
1a1ae60
Attach SessionConfiguration to ATProtocolConfiguration
MasterJ93 Feb 25, 2025
dcd85b0
Merge branch 'develop'
MasterJ93 Feb 27, 2025
2856369
Merge branch 'develop'
MasterJ93 Feb 28, 2025
444fd91
Fix lexicon model
MasterJ93 Mar 1, 2025
55c6965
Delete UpdateListRecord
MasterJ93 Mar 1, 2025
a05aa3b
Tweak documentation
MasterJ93 Mar 1, 2025
3266c11
Fix lexicon model
MasterJ93 Mar 1, 2025
ef4b4e3
Tweak documentation
MasterJ93 Mar 1, 2025
9c2fba9
Add support for adding list records
MasterJ93 Mar 1, 2025
1c1f071
Tweak documentation
MasterJ93 Mar 1, 2025
11e62a0
Add support for updating list records
MasterJ93 Mar 1, 2025
df81ebd
Add initializer for ProfileRecord
MasterJ93 Mar 1, 2025
b391bad
Fix documentation
MasterJ93 Mar 1, 2025
320af2c
Fix lexicon model
MasterJ93 Mar 1, 2025
2372ed3
Tweak createListRecord
MasterJ93 Mar 1, 2025
37b4982
Add helper method for creating strong references
MasterJ93 Mar 1, 2025
9b1e6dd
Tweak createListRecord, updateListRecord
MasterJ93 Mar 1, 2025
d75284e
Add profile record support in ATProtoBluesky
MasterJ93 Mar 1, 2025
f8b2fcc
Merge branch 'develop'
MasterJ93 Mar 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,158 @@ import Foundation

extension ATProtoBluesky {

/// A convenience method to create a list record to the user account in Bluesky.
///
/// This can be used instead of creating your own method if you wish not to do so.
///
/// # Creating a List
///
/// After you authenticate into Bluesky, you can create a list by using the `name` and
/// `listType` arguments:
///
/// ```swift
/// do {
/// let listResult = try await atProtoBluesky.createListRecord(
/// named: "Book Authors",
/// ofType: .reference
/// )
///
/// print(listResult)
/// } catch {
/// throw error
/// }
/// ```
///
/// You can optionally add a description and avatar image for the list.
///
/// - Note: Names can be up to 64 characters long. \
/// \
/// Descriptions can be up to 300 characters long.\
/// \
/// List avatar images can be either .jpg or .png and can be up to 1 MB large.
///
/// # Types of Lists
///
/// There are three types of lists that can be created:
/// - Moderation lists: These are lists where the user accounts listed will be muted
/// or blocked.
/// - Curated lists: These are lists where the user accounts listed are used for curation:
/// things like allowlists for interaction or regular feeds.
/// - Reference feeds: These are lists where the user accounts listed will be used as a
/// reference, such as with a starter pack.
///
/// - Parameters:
/// - name: The name of the list.
/// - listType: The list's type.
/// - description: The list's description. Optional. Defaults to `nil`.
/// - listAvatarImage: The avatar image of the list. Optional. Defaults to `nil`.
/// - labels: An array of labels made by the user. Optional. Defaults to `nil`.
/// - creationDate: The date of the post record. Defaults to `Date.now`.
/// - recordKey: The record key of the collection. Optional. Defaults to `nil`.
/// - shouldValidate: Indicates whether the record should be validated. Optional.
/// Defaults to `true`.
/// - swapCommit: Swaps out an operation based on the CID. Optional. Defaults to `nil`.
/// - Returns: A strong reference, which contains the newly-created record's URI and CID hash.
public func createListRecord(
named name: String,
ofType listType: ListType,
description: String? = nil,
listAvatarImage: ATProtoTools.ImageQuery? = nil,
labels: ATUnion.ListLabelsUnion? = nil,
creationDate: Date = Date(),
recordKey: String? = nil,
shouldValidate: Bool? = true,
swapCommit: String? = nil
) async throws -> ComAtprotoLexicon.Repository.StrongReference {
guard let session else {
throw ATRequestPrepareError.missingActiveSession
}

guard let sessionURL = session.pdsURL else {
throw ATRequestPrepareError.invalidPDS
}

// listPurpose
let listPurpose: AppBskyLexicon.Graph.ListPurpose
switch listType {
case .moderation:
listPurpose = .modlist
case .curation:
listPurpose = .curatelist
case .reference:
listPurpose = .referencelist
}

// name
// Truncate the number of characters to 64.
let nameText = name.truncated(toLength: 64)

// description and descriptionFacets
var descriptionText: String? = nil
var descriptionFacets: [AppBskyLexicon.RichText.Facet]? = nil

if let description = description {
// Truncate the number of characters to 300.
let truncatedDescriptionText = description.truncated(toLength: 300)
descriptionText = truncatedDescriptionText

let facets = await ATFacetParser.parseFacets(from: truncatedDescriptionText, pdsURL: session.pdsURL ?? "https://bsky.social")
descriptionFacets = facets
}

// listAvatarImage
var avatarImage: ComAtprotoLexicon.Repository.UploadBlobOutput? = nil
if let listAvatarImage = listAvatarImage {
let postEmbed = try await uploadImages(
[listAvatarImage],
pdsURL: sessionURL,
accessToken: session.accessToken
)

switch postEmbed {
case .images(let imagesDefinition):
let avatarImageContainer = imagesDefinition
avatarImage = avatarImageContainer.images[0].imageBlob
default:
break
}
}

let listRecord = AppBskyLexicon.Graph.ListRecord(
purpose: listPurpose,
name: nameText,
description: descriptionText,
descriptionFacets: descriptionFacets,
avatarImageBlob: avatarImage,
labels: labels,
createdAt: creationDate
)

do {
return try await atProtoKitInstance.createRecord(
repositoryDID: session.sessionDID,
collection: "app.bsky.graph.list",
recordKey: recordKey ?? nil,
shouldValidate: shouldValidate,
record: UnknownType.record(listRecord),
swapCommit: swapCommit ?? nil
)
} catch {
throw error
}
}

/// The list's type.
public enum ListType {

/// Indicates the list is used for muting or blocking the list of user accounts.
case moderation

/// Indicates the list is used for curation purposes, such as list feeds or
/// interaction gating.
case curation

/// Indicates the list is used for reference purposes (such as within a starter pack).
case reference
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@ import Foundation

extension ATProtoBluesky {

/// Deletes a list record.
///
/// This can also be used to validate if a list record has been deleted.
///
/// - Note: This can be either the URI of the list record, or the full record object itself.
///
/// - Parameter record: The list record that needs to be deleted.
public func deleteListRecord(_ record: RecordIdentifier) async throws {
return try await deleteLikeRecord(record)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//
// UpdateListRecord.swift
//
//
// Created by Christopher Jr Riley on 2025-02-28.
//

import Foundation

extension ATProtoBluesky {

/// A convenience method to update a list record to the user account in Bluesky.
///
/// This can be used instead of creating your own method if you wish not to do so.
///
/// - Parameters:
/// - listURI: The URI of the post.
/// - name: The name of the list.
/// - listType: The list's type.
/// - description: The list's description. Optional. Defaults to `nil`.
/// - listAvatarImage: The avatar image of the list. Optional. Defaults to `nil`.
/// - labels: An array of labels made by the user. Optional. Defaults to `nil`.
/// - Returns: A strong reference, which contains the newly-created record's URI and CID hash.
public func updateListRecord(
listURI: String,
name: String,
listType: ListType,
description: String? = nil,
listAvatarImage: ATProtoTools.ImageQuery? = nil,
labels: ATUnion.ListLabelsUnion? = nil
) async throws -> ComAtprotoLexicon.Repository.StrongReference {
guard let session else {
throw ATRequestPrepareError.missingActiveSession
}

guard let sessionURL = session.pdsURL else {
throw ATRequestPrepareError.invalidPDS
}

// listPurpose
let listPurpose: AppBskyLexicon.Graph.ListPurpose
switch listType {
case .moderation:
listPurpose = .modlist
case .curation:
listPurpose = .curatelist
case .reference:
listPurpose = .referencelist
}

// name
// Truncate the number of characters to 64.
let nameText = name.truncated(toLength: 64)

// description and descriptionFacets
var descriptionText: String? = nil
var descriptionFacets: [AppBskyLexicon.RichText.Facet]? = nil

if let description = description {
// Truncate the number of characters to 300.
let truncatedDescriptionText = description.truncated(toLength: 300)
descriptionText = truncatedDescriptionText

let facets = await ATFacetParser.parseFacets(from: truncatedDescriptionText, pdsURL: session.pdsURL ?? "https://bsky.social")
descriptionFacets = facets
}

// listAvatarImage
var postEmbed: ATUnion.PostEmbedUnion? = nil
var avatarImage: ComAtprotoLexicon.Repository.UploadBlobOutput? = nil
if let listAvatarImage = listAvatarImage {
postEmbed = try await uploadImages(
[listAvatarImage],
pdsURL: sessionURL,
accessToken: session.accessToken
)

switch postEmbed {
case .images(let imagesDefinition):
let avatarImageContainer = imagesDefinition
avatarImage = avatarImageContainer.images[0].imageBlob
default:
break
}
}

let listRecord = AppBskyLexicon.Graph.ListRecord(
purpose: listPurpose,
name: nameText,
description: descriptionText,
descriptionFacets: descriptionFacets,
avatarImageBlob: avatarImage,
labels: labels,
createdAt: Date()
)

do {
let uri = try ATProtoTools().parseURI(listURI)

guard try await atProtoKitInstance.getRepositoryRecord(
from: uri.repository,
collection: uri.collection,
recordKey: uri.recordKey
).value != nil else {
throw ATProtoBlueskyError.postNotFound(message: "List record (\(listURI)) not found.")
}

return try await atProtoKitInstance.putRecord(
repository: session.sessionDID,
collection: "app.bsky.graph.list",
recordKey: uri.recordKey,
record: UnknownType.record(listRecord)
)
} catch {
throw error
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ extension ATProtoBluesky {
///
/// This can be used instead of creating your own method if you wish not to do so.
///
/// ## Creating a Post
/// After you authenticate into Bluesky, you can create a post by using the `text` field:
/// # Creating a Post
/// After you authenticate into Bluesky, you can create a post by using the `text` argument:
/// ```swift
/// do {
/// let postResult = try await atProtoBluesky.createPostRecord(
Expand Down Expand Up @@ -68,11 +68,11 @@ extension ATProtoBluesky {
/// - Note: `startPostion` and `endPostion` must be UTF-8 values.
///
///
/// ## Adding Embedded Content
/// # Adding Embedded Content
/// You can embed various kinds of content in your post, from media to external links,
/// to other records.
///
/// ### Images
/// ## Images
/// Use ``ATProtoTools/ImageQuery`` to add details to the image, such as
/// alt text, then attach it to the post record.
///
Expand All @@ -81,7 +81,7 @@ extension ATProtoBluesky {
/// let image = ATProtoTools.ImageQuery(
/// imageData: Data(contentsOf: "/path/to/file/cat.jpg"),
/// fileName: "cat.jpg",
/// altText: "A cat looking annoyed, waring a hat."
/// altText: "A cat looking annoyed, wearing a hat."
/// )
///
/// let postResult = try await atProtoBluesky.createPostRecord(
Expand All @@ -99,7 +99,7 @@ extension ATProtoBluesky {
///
/// Up to four images can be attached to a post. All images need to be a .jpg format.
///
/// ### Videos
/// ## Videos
/// Similar to images, you can add videos to a post.
///
/// ```swift
Expand All @@ -121,7 +121,7 @@ extension ATProtoBluesky {
/// You can upload up to 25 videos per day and the 25 videos can't exceed a total of 500 MB
/// for the day.
///
/// ### External Links
/// ## External Links
/// You can attach a website card to the post.
///
/// ```swift
Expand Down Expand Up @@ -159,7 +159,7 @@ extension ATProtoBluesky {
/// If there are any links in the post's text, the method will not convert it into a
/// website card; you will need to manually achieve this.
///
/// ## Creating a Quote Post
/// # Creating a Quote Post
/// Quote posts are also embeds: you simply need to embed the record's strong reference to it.
///
/// ```swift
Expand All @@ -180,7 +180,7 @@ extension ATProtoBluesky {
/// Only one record can be embedded. This isn't limited to post records, though: you can embed
/// any Bluesky-related record that you wish.
///
/// ## Creating a Reply
/// # Creating a Reply
/// To create a reply, pass the reply reference of the post's reply reference to the post record.
///
/// ```swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extension ATProtoBluesky {
/// }
/// ```
///
/// ## Managing Embedding Post Options
/// # Managing Embedding Post Options
///
/// You can detact the post from quote posts by using the `detachedEmbeddingURIs` argument.
/// When doing so, Bluesky will display a "Removed by author" warning and the quote post will
Expand Down
Loading