this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

more complete lexicons

+415 -29
+9 -18
Package.resolved
··· 1 1 { 2 - "originHash" : "1cc87e98c4114f0c0c0a6fea792a84d353c9e8d6b11e58b00673b46e6086d02d", 2 + "originHash" : "cbf9e754c518e5eb56690c6dd9d096349450adf8254fc91d657d65f243cfb57f", 3 3 "pins" : [ 4 4 { 5 - "identity" : "coreatprotocol", 6 - "kind" : "remoteSourceControl", 7 - "location" : "https://tangled.org/@sparrowtek.com/CoreATProtocol", 8 - "state" : { 9 - "branch" : "main", 10 - "revision" : "dd141af5a2066e9081cb09b67d4a9da7a73324dd" 11 - } 12 - }, 13 - { 14 5 "identity" : "jwt-kit", 15 6 "kind" : "remoteSourceControl", 16 7 "location" : "https://github.com/vapor/jwt-kit.git", 17 8 "state" : { 18 - "revision" : "b5f82fb9dc238f2fcac53d721a222513a152613c", 19 - "version" : "5.3.0" 9 + "revision" : "aa60a211797306bfb05b752ad7b4bf9f0b50d898", 10 + "version" : "5.4.0" 20 11 } 21 12 }, 22 13 { ··· 42 33 "kind" : "remoteSourceControl", 43 34 "location" : "https://github.com/apple/swift-asn1.git", 44 35 "state" : { 45 - "revision" : "810496cf121e525d660cd0ea89a758740476b85f", 46 - "version" : "1.5.1" 36 + "revision" : "9f542610331815e29cc3821d3b6f488db8715517", 37 + "version" : "1.6.0" 47 38 } 48 39 }, 49 40 { ··· 60 51 "kind" : "remoteSourceControl", 61 52 "location" : "https://github.com/apple/swift-crypto.git", 62 53 "state" : { 63 - "revision" : "6f70fa9eab24c1fd982af18c281c4525d05e3095", 64 - "version" : "4.2.0" 54 + "revision" : "fa308c07a6fa04a727212d793e761460e41049c3", 55 + "version" : "4.3.0" 65 56 } 66 57 }, 67 58 { ··· 69 60 "kind" : "remoteSourceControl", 70 61 "location" : "https://github.com/apple/swift-log.git", 71 62 "state" : { 72 - "revision" : "2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181", 73 - "version" : "1.9.1" 63 + "revision" : "bbd81b6725ae874c69e9b8c8804d462356b55523", 64 + "version" : "1.10.1" 74 65 } 75 66 } 76 67 ],
+2 -2
Package.swift
··· 18 18 ), 19 19 ], 20 20 dependencies: [ 21 - // .package(path: "../CoreATProtocol"), 22 - .package(url: "https://tangled.org/@sparrowtek.com/CoreATProtocol", branch: "main"), 21 + .package(path: "../CoreATProtocol"), 22 + // .package(url: "https://tangled.org/@sparrowtek.com/CoreATProtocol", branch: "main"), 23 23 ], 24 24 targets: [ 25 25 .target(
+77 -7
Sources/bskyKit/BskyAPI.swift
··· 15 15 case getProfile(did: String) 16 16 case getProfiles(dids: [String]) 17 17 case getSuggestions(limit: Int, cursor: String?) 18 - case searchActors(query: String, limit: Int) 18 + case searchActors(query: String, limit: Int, cursor: String?) 19 19 case searchActorsTypeahead(query: String, limit: Int) 20 20 21 21 // Feed endpoints ··· 66 66 case getUnreadCount 67 67 case updateSeen(seenAt: Date) 68 68 69 + // Chat endpoints (proxied through PDS) 70 + case chatListConvos(limit: Int, cursor: String?) 71 + case chatGetConvo(convoId: String) 72 + case chatGetMessages(convoId: String, limit: Int, cursor: String?) 73 + case chatSendMessage(convoId: String, message: Data) 74 + case chatUpdateRead(convoId: String, messageId: String?) 75 + case chatLeaveConvo(convoId: String) 76 + case chatMuteConvo(convoId: String) 77 + case chatUnmuteConvo(convoId: String) 78 + case chatGetConvoForMembers(members: [String]) 79 + 80 + // Moderation 81 + case createReport(body: Data) 82 + 69 83 // Generic endpoints for newer/less-common lexicons. 70 84 case xrpcQuery(id: String, parameters: Parameters) 71 85 case xrpcProcedure(id: String, body: Parameters?) ··· 124 138 case .listNotifications: "/xrpc/app.bsky.notification.listNotifications" 125 139 case .getUnreadCount: "/xrpc/app.bsky.notification.getUnreadCount" 126 140 case .updateSeen: "/xrpc/app.bsky.notification.updateSeen" 141 + // Chat 142 + case .chatListConvos: "/xrpc/chat.bsky.convo.listConvos" 143 + case .chatGetConvo: "/xrpc/chat.bsky.convo.getConvo" 144 + case .chatGetMessages: "/xrpc/chat.bsky.convo.getMessages" 145 + case .chatSendMessage: "/xrpc/chat.bsky.convo.sendMessage" 146 + case .chatUpdateRead: "/xrpc/chat.bsky.convo.updateRead" 147 + case .chatLeaveConvo: "/xrpc/chat.bsky.convo.leaveConvo" 148 + case .chatMuteConvo: "/xrpc/chat.bsky.convo.muteConvo" 149 + case .chatUnmuteConvo: "/xrpc/chat.bsky.convo.unmuteConvo" 150 + case .chatGetConvoForMembers: "/xrpc/chat.bsky.convo.getConvoForMembers" 151 + // Moderation 152 + case .createReport: "/xrpc/com.atproto.moderation.createReport" 127 153 case .xrpcQuery(let id, _), .xrpcProcedure(let id, _), .xrpcDataProcedure(let id, _, _, _): 128 154 "/xrpc/\(id)" 129 155 } ··· 135 161 .getFeed, .getActorFeeds, .getFeedGenerator, .getFeedGenerators, .getListFeed, .getQuotes, .getSuggestedFeeds, 136 162 .getTimeline, .getAuthorFeed, .getPostThread, .getPosts, .searchPosts, .getActorLikes, .getLikes, .getRepostedBy, 137 163 .getFollows, .getFollowers, .getBlocks, .getMutes, .getRelationships, 138 - .listNotifications, .getUnreadCount: 164 + .listNotifications, .getUnreadCount, 165 + .chatListConvos, .chatGetConvo, .chatGetMessages, .chatGetConvoForMembers: 139 166 return .get 140 167 case .updateSeen, .muteActor, .unmuteActor, .muteThread, .unmuteThread, .muteActorList, .unmuteActorList, 168 + .chatSendMessage, .chatUpdateRead, .chatLeaveConvo, .chatMuteConvo, .chatUnmuteConvo, 169 + .createReport, 141 170 .xrpcProcedure, .xrpcDataProcedure: 142 171 return .post 143 172 case .xrpcQuery: ··· 162 191 if let cursor { params["cursor"] = cursor } 163 192 return .requestParameters(encoding: .urlEncoding(parameters: params)) 164 193 165 - case .searchActors(let query, let limit): 166 - return .requestParameters(encoding: .urlEncoding(parameters: [ 167 - "q": query, 168 - "limit": limit 169 - ])) 194 + case .searchActors(let query, let limit, let cursor): 195 + var params: Parameters = ["q": query, "limit": limit] 196 + if let cursor { params["cursor"] = cursor } 197 + return .requestParameters(encoding: .urlEncoding(parameters: params)) 170 198 171 199 case .searchActorsTypeahead(let query, let limit): 172 200 return .requestParameters(encoding: .urlEncoding(parameters: [ ··· 318 346 "seenAt": formatter.string(from: seenAt) 319 347 ])) 320 348 349 + // Chat endpoints 350 + case .chatListConvos(let limit, let cursor): 351 + var params: Parameters = ["limit": limit] 352 + if let cursor { params["cursor"] = cursor } 353 + return .requestParameters(encoding: .urlEncoding(parameters: params)) 354 + 355 + case .chatGetConvo(let convoId): 356 + return .requestParameters(encoding: .urlEncoding(parameters: ["convoId": convoId])) 357 + 358 + case .chatGetMessages(let convoId, let limit, let cursor): 359 + var params: Parameters = ["convoId": convoId, "limit": limit] 360 + if let cursor { params["cursor"] = cursor } 361 + return .requestParameters(encoding: .urlEncoding(parameters: params)) 362 + 363 + case .chatSendMessage(_, let message): 364 + return .requestParameters(encoding: .jsonDataEncoding(data: message)) 365 + 366 + case .chatUpdateRead(let convoId, let messageId): 367 + var params: Parameters = ["convoId": convoId] 368 + if let messageId { params["messageId"] = messageId } 369 + return .requestParameters(encoding: .jsonEncoding(parameters: params)) 370 + 371 + case .chatLeaveConvo(let convoId): 372 + return .requestParameters(encoding: .jsonEncoding(parameters: ["convoId": convoId])) 373 + 374 + case .chatMuteConvo(let convoId): 375 + return .requestParameters(encoding: .jsonEncoding(parameters: ["convoId": convoId])) 376 + 377 + case .chatUnmuteConvo(let convoId): 378 + return .requestParameters(encoding: .jsonEncoding(parameters: ["convoId": convoId])) 379 + 380 + case .chatGetConvoForMembers(let members): 381 + return .requestParameters(encoding: .urlEncoding(parameters: ["members": members])) 382 + 383 + // Moderation 384 + case .createReport(let body): 385 + return .requestParameters(encoding: .jsonDataEncoding(data: body)) 386 + 321 387 case .xrpcQuery(_, let parameters): 322 388 if parameters.isEmpty { 323 389 return .request ··· 339 405 var headers: HTTPHeaders = ["Content-Type": contentType] 340 406 if let accept { headers["Accept"] = accept } 341 407 return headers 408 + case .chatListConvos, .chatGetConvo, .chatGetMessages, .chatSendMessage, 409 + .chatUpdateRead, .chatLeaveConvo, .chatMuteConvo, .chatUnmuteConvo, 410 + .chatGetConvoForMembers: 411 + return ["atproto-proxy": "did:web:api.bsky.chat#bsky_chat"] 342 412 default: 343 413 return nil 344 414 }
+94 -2
Sources/bskyKit/BskyService.swift
··· 138 138 /// - limit: Maximum number of results to return (default: 25, max: 100). 139 139 /// - Returns: Matching user profiles with optional cursor for pagination. 140 140 /// - Throws: An error if the request fails. 141 - public func searchActors(query: String, limit: Int = 25) async throws -> SearchActorsResult { 142 - try await execute(.searchActors(query: query, limit: limit)) 141 + public func searchActors(query: String, limit: Int = 25, cursor: String? = nil) async throws -> SearchActorsResult { 142 + try await execute(.searchActors(query: query, limit: limit, cursor: cursor)) 143 143 } 144 144 145 145 /// Fast search for autocomplete functionality. ··· 405 405 /// - Throws: An error if not authenticated or the request fails. 406 406 public func updateSeen(at date: Date = Date()) async throws { 407 407 let _: EmptyResponse = try await execute(.updateSeen(seenAt: date)) 408 + } 409 + 410 + // MARK: - Chat 411 + 412 + /// Lists the user's conversations. 413 + public func listConvos(limit: Int = 50, cursor: String? = nil) async throws -> ConvoListResponse { 414 + try await execute(.chatListConvos(limit: limit, cursor: cursor)) 415 + } 416 + 417 + /// Gets a single conversation by ID. 418 + public func getConvo(convoId: String) async throws -> GetConvoForMembersResponse { 419 + try await execute(.chatGetConvo(convoId: convoId)) 420 + } 421 + 422 + /// Gets messages in a conversation. 423 + public func getMessages(convoId: String, limit: Int = 50, cursor: String? = nil) async throws -> ChatMessagesResponse { 424 + try await execute(.chatGetMessages(convoId: convoId, limit: limit, cursor: cursor)) 425 + } 426 + 427 + /// Sends a message in a conversation. 428 + public func sendMessage(convoId: String, text: String) async throws -> ChatSendMessageResponse { 429 + let body: [String: Any] = [ 430 + "convoId": convoId, 431 + "message": ["text": text] 432 + ] 433 + let data = try JSONSerialization.data(withJSONObject: body) 434 + return try await execute(.chatSendMessage(convoId: convoId, message: data)) 435 + } 436 + 437 + /// Marks a conversation as read. 438 + public func updateConvoRead(convoId: String, messageId: String? = nil) async throws -> UpdateReadResponse { 439 + try await execute(.chatUpdateRead(convoId: convoId, messageId: messageId)) 440 + } 441 + 442 + /// Leaves a conversation. 443 + public func leaveConvo(convoId: String) async throws -> LeaveConvoResponse { 444 + try await execute(.chatLeaveConvo(convoId: convoId)) 445 + } 446 + 447 + /// Mutes a conversation. 448 + public func muteConvo(convoId: String) async throws -> GetConvoForMembersResponse { 449 + try await execute(.chatMuteConvo(convoId: convoId)) 450 + } 451 + 452 + /// Unmutes a conversation. 453 + public func unmuteConvo(convoId: String) async throws -> GetConvoForMembersResponse { 454 + try await execute(.chatUnmuteConvo(convoId: convoId)) 455 + } 456 + 457 + /// Gets or creates a conversation with the given members. 458 + public func getConvoForMembers(members: [String]) async throws -> GetConvoForMembersResponse { 459 + try await execute(.chatGetConvoForMembers(members: members)) 460 + } 461 + 462 + // MARK: - Moderation 463 + 464 + /// Reports a post or user. 465 + public func createReport(reasonType: String, reason: String?, subjectDid: String?, subjectUri: String?, subjectCid: String?) async throws -> JSONValue { 466 + var subject: [String: Any] = [:] 467 + if let subjectUri, let subjectCid { 468 + subject["$type"] = "com.atproto.repo.strongRef" 469 + subject["uri"] = subjectUri 470 + subject["cid"] = subjectCid 471 + } else if let subjectDid { 472 + subject["$type"] = "com.atproto.admin.defs#repoRef" 473 + subject["did"] = subjectDid 474 + } 475 + 476 + var body: [String: Any] = [ 477 + "reasonType": reasonType, 478 + "subject": subject 479 + ] 480 + if let reason { body["reason"] = reason } 481 + 482 + let data = try JSONSerialization.data(withJSONObject: body) 483 + return try await execute(.createReport(body: data)) 484 + } 485 + 486 + // MARK: - Typed List Methods 487 + 488 + /// Fetches lists created by an actor with typed response. 489 + public func getListsTyped(for actor: String, limit: Int = 50, cursor: String? = nil) async throws -> GraphListsResponse { 490 + var params: Parameters = ["actor": actor, "limit": limit] 491 + if let cursor { params["cursor"] = cursor } 492 + return try await executeQuery("app.bsky.graph.getLists", parameters: params) 493 + } 494 + 495 + /// Fetches a list and its membership with typed response. 496 + public func getListTyped(uri: String, limit: Int = 50, cursor: String? = nil) async throws -> GraphListResponse { 497 + var params: Parameters = ["list": uri, "limit": limit] 498 + if let cursor { params["cursor"] = cursor } 499 + return try await executeQuery("app.bsky.graph.getList", parameters: params) 408 500 } 409 501 410 502 // MARK: - Priority 2: Account, Bookmark, and Graph Collections
+120
Sources/bskyKit/Models/Chat.swift
··· 1 + import Foundation 2 + 3 + /// Response from chat.bsky.convo.listConvos 4 + public struct ConvoListResponse: Codable, Sendable { 5 + public let convos: [ConvoView] 6 + public let cursor: String? 7 + } 8 + 9 + /// A conversation view 10 + public struct ConvoView: Codable, Sendable, Identifiable { 11 + public let id: String 12 + public let rev: String 13 + public let members: [ChatMember] 14 + public let lastMessage: ChatMessageUnion? 15 + public let muted: Bool 16 + public let unreadCount: Int 17 + } 18 + 19 + /// Union type for messages 20 + public enum ChatMessageUnion: Codable, Sendable { 21 + case message(ChatMessageView) 22 + case deleted(DeletedMessageView) 23 + case unknown 24 + 25 + enum CodingKeys: String, CodingKey { 26 + case type = "$type" 27 + } 28 + 29 + public init(from decoder: Decoder) throws { 30 + let container = try decoder.container(keyedBy: CodingKeys.self) 31 + let type = try container.decodeIfPresent(String.self, forKey: .type) 32 + 33 + switch type { 34 + case "chat.bsky.convo.defs#messageView": 35 + self = .message(try ChatMessageView(from: decoder)) 36 + case "chat.bsky.convo.defs#deletedMessageView": 37 + self = .deleted(try DeletedMessageView(from: decoder)) 38 + default: 39 + self = .unknown 40 + } 41 + } 42 + 43 + public func encode(to encoder: Encoder) throws { 44 + switch self { 45 + case .message(let msg): 46 + try msg.encode(to: encoder) 47 + case .deleted(let msg): 48 + try msg.encode(to: encoder) 49 + case .unknown: 50 + var container = encoder.singleValueContainer() 51 + try container.encode([String: String]()) 52 + } 53 + } 54 + } 55 + 56 + /// A chat message 57 + public struct ChatMessageView: Codable, Sendable, Identifiable { 58 + public let id: String 59 + public let rev: String 60 + public let text: String 61 + public let facets: [Facet]? 62 + public let sender: ChatSender 63 + public let sentAt: Date 64 + } 65 + 66 + /// Deleted message placeholder 67 + public struct DeletedMessageView: Codable, Sendable, Identifiable { 68 + public let id: String 69 + public let rev: String 70 + public let sender: ChatSender 71 + public let sentAt: Date 72 + } 73 + 74 + /// Chat message sender 75 + public struct ChatSender: Codable, Sendable { 76 + public let did: String 77 + } 78 + 79 + /// Chat member profile 80 + public struct ChatMember: Codable, Sendable, Identifiable { 81 + public let did: String 82 + public let handle: String 83 + public let displayName: String? 84 + public let avatar: String? 85 + public let chatDisabled: Bool? 86 + 87 + public var id: String { did } 88 + } 89 + 90 + /// Response from chat.bsky.convo.getMessages 91 + public struct ChatMessagesResponse: Codable, Sendable { 92 + public let messages: [ChatMessageUnion] 93 + public let cursor: String? 94 + } 95 + 96 + /// Response from chat.bsky.convo.sendMessage 97 + public struct ChatSendMessageResponse: Codable, Sendable { 98 + public let id: String 99 + public let rev: String 100 + public let text: String 101 + public let facets: [Facet]? 102 + public let sender: ChatSender 103 + public let sentAt: Date 104 + } 105 + 106 + /// Response from chat.bsky.convo.getConvoForMembers 107 + public struct GetConvoForMembersResponse: Codable, Sendable { 108 + public let convo: ConvoView 109 + } 110 + 111 + /// Response from chat.bsky.convo.updateRead 112 + public struct UpdateReadResponse: Codable, Sendable { 113 + public let convo: ConvoView 114 + } 115 + 116 + /// Response from chat.bsky.convo.leaveConvo 117 + public struct LeaveConvoResponse: Codable, Sendable { 118 + public let convoId: String 119 + public let rev: String 120 + }
+67
Sources/bskyKit/Models/Lists.swift
··· 1 + import Foundation 2 + 3 + /// Response from app.bsky.graph.getLists 4 + public struct GraphListsResponse: Codable, Sendable { 5 + public let lists: [GraphListView] 6 + public let cursor: String? 7 + } 8 + 9 + /// Response from app.bsky.graph.getList 10 + public struct GraphListResponse: Codable, Sendable { 11 + public let list: GraphListView 12 + public let items: [GraphListItemView] 13 + public let cursor: String? 14 + } 15 + 16 + /// A list view 17 + public struct GraphListView: Codable, Sendable, Identifiable { 18 + public let uri: String 19 + public let cid: String 20 + public let creator: Author 21 + public let name: String 22 + public let purpose: String 23 + public let description: String? 24 + public let descriptionFacets: [Facet]? 25 + public let avatar: String? 26 + public let listItemCount: Int? 27 + public let indexedAt: Date? 28 + public let viewer: ListViewer? 29 + public let labels: [AuthorLabels]? 30 + 31 + public var id: String { uri } 32 + 33 + public var purposeLabel: String { 34 + switch purpose { 35 + case "app.bsky.graph.defs#curatelist": 36 + return "Curation" 37 + case "app.bsky.graph.defs#modlist": 38 + return "Moderation" 39 + case "app.bsky.graph.defs#referencelist": 40 + return "Reference" 41 + default: 42 + return "List" 43 + } 44 + } 45 + 46 + public var isCurationList: Bool { 47 + purpose == "app.bsky.graph.defs#curatelist" 48 + } 49 + 50 + public var isModList: Bool { 51 + purpose == "app.bsky.graph.defs#modlist" 52 + } 53 + } 54 + 55 + /// Viewer state for a list 56 + public struct ListViewer: Codable, Sendable { 57 + public let muted: Bool? 58 + public let blocked: String? 59 + } 60 + 61 + /// An item (member) in a list 62 + public struct GraphListItemView: Codable, Sendable, Identifiable { 63 + public let uri: String 64 + public let subject: ActorProfile 65 + 66 + public var id: String { uri } 67 + }
+7
Sources/bskyKit/Models/Preferences.swift
··· 28 28 public let type: String 29 29 public let value: String 30 30 public let pinned: Bool 31 + 32 + public init(id: String, type: String, value: String, pinned: Bool) { 33 + self.id = id 34 + self.type = type 35 + self.value = value 36 + self.pinned = pinned 37 + } 31 38 } 32 39 33 40 // MARK: - Convenience
+39
Sources/bskyKit/RepoService.swift
··· 201 201 try await deleteRecord(repo: repo, collection: "app.bsky.graph.follow", rkey: rkey) 202 202 } 203 203 204 + /// Creates a list 205 + public func createList(repo: String, name: String, purpose: String, description: String?) async throws -> CreateRecordResponse { 206 + var record: [String: Any] = [ 207 + "$type": "app.bsky.graph.list", 208 + "name": name, 209 + "purpose": purpose, 210 + "createdAt": ISO8601DateFormatter().string(from: Date()) 211 + ] 212 + if let description { record["description"] = description } 213 + return try await createRecord(repo: repo, collection: "app.bsky.graph.list", record: record) 214 + } 215 + 216 + /// Deletes a list 217 + public func deleteList(uri: String, repo: String) async throws { 218 + guard let rkey = extractRkey(from: uri) else { 219 + throw RepoError.invalidUri(uri) 220 + } 221 + try await deleteRecord(repo: repo, collection: "app.bsky.graph.list", rkey: rkey) 222 + } 223 + 224 + /// Adds a user to a list 225 + public func addListItem(listUri: String, subjectDid: String, repo: String) async throws -> CreateRecordResponse { 226 + let record: [String: Any] = [ 227 + "$type": "app.bsky.graph.listitem", 228 + "subject": subjectDid, 229 + "list": listUri, 230 + "createdAt": ISO8601DateFormatter().string(from: Date()) 231 + ] 232 + return try await createRecord(repo: repo, collection: "app.bsky.graph.listitem", record: record) 233 + } 234 + 235 + /// Removes a user from a list 236 + public func removeListItem(uri: String, repo: String) async throws { 237 + guard let rkey = extractRkey(from: uri) else { 238 + throw RepoError.invalidUri(uri) 239 + } 240 + try await deleteRecord(repo: repo, collection: "app.bsky.graph.listitem", rkey: rkey) 241 + } 242 + 204 243 /// Blocks a user 205 244 public func block(did: String, repo: String) async throws -> CreateRecordResponse { 206 245 let record: [String: Any] = [