Use atproto actions with ease in iOS shortcuts
1//
2// GetARecordIntent.swift
3// shortcut
4//
5// Created by Bailey Townsend on 6/30/25.
6//
7
8import ATProtoKit
9import AppIntents
10import SwiftData
11import SwiftUI
12import UniformTypeIdentifiers
13
14struct GetRepoIntent: AppIntent {
15
16 @Parameter(
17 title: "Repo",
18 description: "The handle or DID of the repo"
19 )
20 var atIdentifier: String
21
22 @Parameter(
23 title: "Since",
24 description: "A TID created from a timestamp of since when to get the record")
25 var since: String?
26
27 static let title: LocalizedStringResource = "Get Repo"
28
29 static let description: IntentDescription = IntentDescription(
30 stringLiteral:
31 "Get's the users repo as a .CAR file. This can also be used as a backup of your repo, but will not include the account's blobs (pictures, videos, etc)"
32 )
33
34 static var parameterSummary: some ParameterSummary {
35 Summary(
36 "Download a CAR export of \(\.$atIdentifier)'s repo"
37 ) {
38 \.$since
39 }
40 }
41
42 @Dependency
43 private var blobDownloader: BlobDownloader
44
45 func perform() async throws -> some ReturnsValue<RepoCarEntity> {
46 let atProtoManager = AtProtocolManager()
47 let lowerCaseAtIdentifier = self.atIdentifier.lowercased().trim()
48 let info = try await GetRemoteInfo.getRemoteRepoInfo(
49 possibleRepo: lowerCaseAtIdentifier, atProtoManager: atProtoManager)
50
51 let fileManager = FileManager.default
52 let saveLocation = fileManager.temporaryDirectory
53 let date = Date()
54 let formatter = ISO8601DateFormatter()
55 let utcString = formatter.string(from: date)
56 let fileName = "\(info.handle)-\(utcString).car"
57
58 do {
59
60 ///Just to be on the safe side
61 await self.blobDownloader.CancelAll()
62 let response = try await self.blobDownloader.getCar(
63 from: info.repo, since: self.since, fileManager: fileManager,
64 saveLocation: saveLocation, fileName: fileName, pdsURL: info.pdsURL)
65 let record: RepoCarEntity = RepoCarEntity()
66
67 record.car = IntentFile(
68 fileURL: response
69 )
70
71 return .result(
72 value: record)
73 } catch let shortCutError as ShortcutErrors {
74 switch shortCutError {
75 case .NoSession:
76 throw GenericIntentError.message("No session found")
77 case .ErrorCreatingARecord(let errorMessage):
78 throw GenericIntentError.message(errorMessage)
79 case .AuthError(let authError):
80 throw GenericIntentError.message(authError)
81 }
82 } catch let shortCutError as GenericIntentError {
83 throw shortCutError
84 } catch let blobDownloadError as BlobDownloadError {
85 switch blobDownloadError {
86 case .apiError(let error):
87 throw GenericIntentError.message(error.message)
88 default:
89 throw GenericIntentError.general
90 }
91 } catch {
92 throw GenericIntentError.general
93 }
94 }
95
96}