Use atproto actions with ease in iOS shortcuts
at main 4.4 kB view raw
1// 2// CreateARecordIntent.swift 3// shortcut 4// 5// Created by Bailey Townsend on 6/30/25. 6// 7 8import ATCommonWeb 9import ATProtoKit 10import AppIntents 11import SwiftData 12import SwiftUI 13 14struct PutARecordIntent: AppIntent { 15 16 @Parameter( 17 title: "AT Identifier", 18 description: 19 "The saved AT Identifier of the account you want to use to authenticate with and write the record to" 20 ) 21 var atIdentifier: AtIdentifierAppEntity 22 23 @Parameter( 24 title: "Collection", 25 description: "The collection you want to write to, like app.bsky.feed.post", ) 26 var collection: String 27 28 @Parameter( 29 title: "Record Key", 30 description: 31 "The record key for the new post, optional. A tid will be used if not provided", 32 default: nil) 33 var recordKey: String? 34 35 @Parameter( 36 title: "Should Validate", 37 description: 38 "You will probably not use this unless you are writing known atproto records. i.e. the ones found in the atproto repo", 39 default: false 40 ) 41 var shouldValidate: Bool 42 43 @Parameter( 44 title: "Record", 45 description: 46 "This is most likely a Dictionary Variable, or a JSON file. But it is the supplied atproto record. We add the $type from the type parameter to this record", 47 ) 48 var record: IntentFile 49 50 static let title: LocalizedStringResource = "Put a Record" 51 52 static let description: IntentDescription? = 53 "Either updates a record or create one if it doesn't exist" 54 55 static var parameterSummary: some ParameterSummary { 56 Summary( 57 "Update or create a \(\.$collection) record for \(\.$atIdentifier) with \(\.$record)" 58 ) { 59 \.$recordKey 60 \.$shouldValidate 61 } 62 } 63 64 func perform() async throws -> some ReturnsValue<StrongReferenceAppEntity> { 65 66 do { 67 let lowercaseType = self.collection.lowercased() 68 69 let decoder = JSONDecoder() 70 71 guard 72 case .dictionary(var dict) = try decoder.decode( 73 CodableValue.self, from: record.data) 74 else { 75 throw GenericIntentError.message("Could not parse JSON") 76 } 77 dict["$type"] = CodableValue(stringLiteral: lowercaseType) 78 79 let unknownRecord = UnknownType.unknown(dict) 80 81 let atProtoManager = AtProtocolManager() 82 var putRecordKey = "" 83 if let recordKey = self.recordKey { 84 if recordKey.isEmpty { 85 var generator = TIDGenerator() 86 putRecordKey = generator.generateTID() 87 } else { 88 putRecordKey = self.recordKey ?? "" 89 } 90 } else { 91 var generator = TIDGenerator() 92 putRecordKey = generator.generateTID() 93 } 94 95 let recordref = try await atProtoManager.putARecord( 96 sessionId: self.atIdentifier.id, 97 repositoryDID: self.atIdentifier.did.lowercased(), 98 collection: lowercaseType, recordKey: putRecordKey, 99 record: unknownRecord) 100 101 let strongRef: StrongReferenceAppEntity = StrongReferenceAppEntity() 102 strongRef.recordCID = recordref.recordCID 103 strongRef.recordURI = recordref.recordURI 104 105 return .result( 106 value: strongRef) 107 } catch let shortCutError as ShortcutErrors { 108 switch shortCutError { 109 case .NoSession: 110 throw GenericIntentError.message("No session found") 111 case .ErrorCreatingARecord(let errorMessage): 112 throw GenericIntentError.message(errorMessage) 113 case .AuthError(let authError): 114 throw GenericIntentError.message(authError) 115 } 116 } catch let shortCutError as GenericIntentError { 117 throw shortCutError 118 119 } catch _ as DecodingError { 120 throw GenericIntentError.message( 121 "There was an error decoding the record. Please make sure your record is valid JSON or use a shortcut dictionary." 122 ) 123 } catch { 124 throw GenericIntentError.general 125 } 126 // return .result(dialog: "Okay, making a Bluesky Post.") 127 } 128}