├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Package.swift ├── README.md ├── Sources └── Smail │ ├── GmailWrapper.swift │ ├── ResourceTypes │ ├── Draft.swift │ ├── Labels.swift │ ├── Message.swift │ ├── Settings.swift │ └── Thread.swift │ ├── Services │ └── REST-API.swift │ ├── Smail.swift │ └── Utils │ └── Helpers.swift └── Tests ├── LinuxMain.swift └── SmailTests ├── SmailTests.swift └── XCTestManifests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Smail", 8 | platforms: [ 9 | .macOS(.v10_15), 10 | .iOS(.v13) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries a package produces, and make them visible to other packages. 14 | .library( 15 | name: "Smail", 16 | targets: ["Smail"]), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | // .package(url: /* package url */, from: "1.0.0"), 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "Smail", 27 | dependencies: []), 28 | .testTarget( 29 | name: "SmailTests", 30 | dependencies: ["Smail"]), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gmail for Swift 2 | 3 | Google hasn't included Swift in their list of platforms supported, hence, Gmail-for-Swift! 4 | 5 | ## WIP 6 | ### API Modules 7 | - [X] Users 8 | - [X] UsersDrafts 9 | - [X] UsersHistory 10 | - [X] UsersLabels 11 | - [X] UsersMessages 12 | - [ ] UsersMessagesAttachments 13 | - [X] UsersSettings 14 | - [ ] UsersSettingsDelegates 15 | - [ ] UsersSettingsFilters 16 | - [ ] UsersSettingsForwardingAddresses 17 | - [ ] UsersSettingsSendAs 18 | - [ ] UsersSettingsSendAssmimeInfo 19 | - [X] UsersThreads 20 | --- 21 | ### Documentation 22 | - [ ] Users 23 | - [ ] UsersDrafts 24 | - [ ] UsersHistory 25 | - [ ] UsersLabels 26 | - [ ] UsersMessages 27 | - [ ] UsersMessagesAttachments 28 | - [ ] UsersSettings 29 | - [ ] UsersSettingsDelegates 30 | - [ ] UsersSettingsFilters 31 | - [ ] UsersSettingsForwardingAddresses 32 | - [ ] UsersSettingsSendAs 33 | - [ ] UsersSettingsSendAssmimeInfo 34 | - [ ] UsersThreads 35 | -------------------------------------------------------------------------------- /Sources/Smail/GmailWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GmailWrapper.swift 3 | // Smail 4 | // 5 | // Created by Praneet S on 12/04/21. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | public class Gmail { 12 | 13 | private static var bearerToken: String = "" 14 | private static var defaultHeadersWithAuth = ["Authorization" : "Bearer \(Gmail.bearerToken)"] 15 | 16 | public static func setAuth(bearerToken: String) { 17 | Gmail.bearerToken = bearerToken 18 | } 19 | 20 | public class Users { 21 | 22 | public static func getProfile(userID: String = "me") -> AnyPublisher { 23 | return API.executeRequest(APIRequest: API.user.getProfile(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self) 24 | } 25 | 26 | public static func stop(userID: String = "me") -> AnyPublisher { 27 | return API.executeRequest(APIRequest: API.user.stop(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self) 28 | } 29 | 30 | public static func watch(userID: String = "me", requestBody: [String : Any]) -> AnyPublisher { 31 | return API.executeRequest(APIRequest: API.user.watch(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: requestBody, decodingType: Data.self) 32 | } 33 | 34 | } 35 | 36 | public class UsersDrafts { 37 | 38 | public static func create(userID: String = "me", type: API.resourceContentType, draft: [String : Any]) -> AnyPublisher { 39 | return API.executeRequest(APIRequest: API.usersDrafts.create(userId: userID, type: type).request, headers: defaultHeadersWithAuth, requestBody: draft, decodingType: Data.self) 40 | } 41 | 42 | public static func delete(userID: String = "me", id: String) -> AnyPublisher { 43 | return API.executeRequest(APIRequest: API.usersDrafts.delete(userId: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self) 44 | } 45 | 46 | public static func get(userID: String = "me", id: String) -> AnyPublisher { 47 | return API.executeRequest(APIRequest: API.usersDrafts.get(userId: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self) 48 | } 49 | 50 | public static func list(userID: String = "me") -> AnyPublisher { 51 | return API.executeRequest(APIRequest: API.usersDrafts.list(userId: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: DraftList.self) 52 | } 53 | 54 | public static func send(userID: String = "me", headers: [String : String], draft: [String : Any], type: API.resourceContentType) -> AnyPublisher { 55 | var headersWithAuth = headers 56 | headersWithAuth["Authorization"] = "Bearer \(Gmail.bearerToken)" 57 | return API.executeRequest(APIRequest: API.usersDrafts.send(userId: userID, type: type).request, headers: headersWithAuth, requestBody: draft, decodingType: Data.self) 58 | } 59 | 60 | public static func update(userID: String = "me", draft: [String : Any], id: String, type: API.resourceContentType) -> AnyPublisher { 61 | return API.executeRequest(APIRequest: API.usersDrafts.update(userId: userID, id: id, type: type).request, headers: defaultHeadersWithAuth, requestBody: draft, decodingType: Data.self) 62 | } 63 | 64 | } 65 | 66 | public class UsersHistory { 67 | 68 | public static func list(userID: String = "me", startHistoryId: String) -> AnyPublisher { 69 | return API.executeRequest(APIRequest: API.usersHistory.list(userID: userID, startHistoryId: startHistoryId).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self) 70 | } 71 | 72 | } 73 | 74 | public class UsersLabels { 75 | 76 | public static func create(userID: String = "me", requestBody: Label) -> AnyPublisher { 77 | return API.executeRequest(APIRequest: API.usersLabels.create(userId: userID).request, headers: defaultHeadersWithAuth, requestBody: requestBody.dictionary, decodingType: Label.self) 78 | } 79 | 80 | public static func delete(userID: String = "me", id: String) -> AnyPublisher { 81 | return API.executeRequest(APIRequest: API.usersLabels.delete(userId: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self) 82 | } 83 | 84 | public static func get(userID: String = "me", id: String) -> AnyPublisher { 85 | return API.executeRequest(APIRequest: API.usersLabels.get(userId: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Label.self) 86 | } 87 | 88 | public static func list(userID: String = "me") -> AnyPublisher { 89 | return API.executeRequest(APIRequest: API.usersLabels.list(userId: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: LabelList.self) 90 | } 91 | 92 | public static func patch(userID: String = "me", id: String, requestBody: Label) -> AnyPublisher { 93 | return API.executeRequest(APIRequest: API.usersLabels.patch(userId: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: requestBody.dictionary, decodingType: Label.self) 94 | } 95 | 96 | public static func update(userID: String = "me", id: String, requestBody: Label) -> AnyPublisher { 97 | return API.executeRequest(APIRequest: API.usersLabels.update(userId: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: requestBody.dictionary, decodingType: Label.self) 98 | } 99 | 100 | } 101 | 102 | public class UsersMessages { 103 | 104 | public static func batchDelete(userID: String = "me", ids: [String]) -> AnyPublisher { 105 | return (API.executeRequest(APIRequest: API.usersMessages.batchDelete(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: ["ids":ids], decodingType: Data.self)) 106 | } 107 | 108 | public static func batchModify(userID: String = "me", requestBody: UserMessagesBatchModifyBody) -> AnyPublisher { 109 | return (API.executeRequest(APIRequest: API.usersMessages.batchModify(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: requestBody.dictionary, decodingType: Data.self)) 110 | } 111 | 112 | public static func delete(userID: String = "me", id: String) -> AnyPublisher { 113 | return (API.executeRequest(APIRequest: API.usersMessages.delete(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self)) 114 | } 115 | 116 | public static func importMessage(userID: String = "me", type: API.resourceContentType, message: Message) -> AnyPublisher { 117 | return API.executeRequest(APIRequest: API.usersMessages.importMessages(userID: userID, importType: type).request, headers: defaultHeadersWithAuth, requestBody: message.dictionary, decodingType: Message.self) 118 | } 119 | 120 | public static func insert(userID: String = "me", type: API.resourceContentType, message: Message) -> AnyPublisher { 121 | return API.executeRequest(APIRequest: API.usersMessages.insert(userID: userID, importType: type).request, headers: defaultHeadersWithAuth, requestBody: message.dictionary, decodingType: Message.self) 122 | } 123 | 124 | public static func modify(userID: String = "me", id: String, modifiedMessageBody: ModifiedMessageBody) -> AnyPublisher { 125 | return API.executeRequest(APIRequest: API.usersMessages.modify(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: modifiedMessageBody.dictionary, decodingType: Message.self) 126 | } 127 | 128 | public static func send(userID: String = "me", type: API.resourceContentType, message: Message) -> AnyPublisher { 129 | return API.executeRequest(APIRequest: API.usersMessages.send(userID: userID, importType: type).request, headers: defaultHeadersWithAuth, requestBody: message.dictionary, decodingType: Message.self) 130 | } 131 | 132 | public static func trash(userID: String = "me", id: String) -> AnyPublisher { 133 | return API.executeRequest(APIRequest: API.usersMessages.trash(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Message.self) 134 | } 135 | 136 | public static func untrash(userID: String = "me", id: String) -> AnyPublisher { 137 | return API.executeRequest(APIRequest: API.usersMessages.untrash(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Message.self) 138 | } 139 | 140 | public static func list(userID: String = "me") -> AnyPublisher { 141 | return API.executeRequest(APIRequest: API.usersMessages.list(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: MessagesList.self) 142 | } 143 | 144 | public static func list(userID: String = "me") async -> MessagesList? { 145 | return await API.executeRequest(APIRequest: API.usersMessages.list(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: MessagesList.self) 146 | } 147 | 148 | public static func get(userID: String = "me", id: String) -> AnyPublisher { 149 | return API.executeRequest(APIRequest: API.usersMessages.get(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Message.self) 150 | } 151 | 152 | } 153 | 154 | public class UsersThreads { 155 | 156 | public static func get(userID: String = "me", id: String) -> AnyPublisher { 157 | return API.executeRequest(APIRequest: API.usersThreads.get(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: MailThread.self) 158 | } 159 | 160 | public static func get(userID: String = "me", id: String) async -> MailThread? { 161 | return await API.executeRequest(APIRequest: API.usersThreads.get(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: MailThread.self) 162 | } 163 | 164 | public static func list(userID: String = "me", maxResults: Int?, pageToken: String?, query: String?, labelIDs: String?, includeSpamTrash: Bool?) -> AnyPublisher { 165 | return API.executeRequest(APIRequest: API.usersThreads.list(userID: userID, maxResults: maxResults, pageToken: pageToken, query: query, labelIDs: labelIDs, includeSpamTrash: includeSpamTrash).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: ThreadList.self) 166 | } 167 | 168 | public static func list(userID: String = "me", maxResults: Int?, pageToken: String?, query: String?, labelIDs: String?, includeSpamTrash: Bool?) async -> ThreadList? { 169 | return await API.executeRequest(APIRequest: API.usersThreads.list(userID: userID, maxResults: maxResults, pageToken: pageToken, query: query, labelIDs: labelIDs, includeSpamTrash: includeSpamTrash).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: ThreadList.self) 170 | } 171 | 172 | public static func trash(userID: String = "me", id: String) -> AnyPublisher { 173 | return API.executeRequest(APIRequest: API.usersThreads.trash(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: MailThread.self) 174 | } 175 | 176 | public static func untrash(userID: String = "me", id: String) -> AnyPublisher { 177 | return API.executeRequest(APIRequest: API.usersThreads.untrash(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: MailThread.self) 178 | } 179 | 180 | public static func delete(userID: String = "me", id: String) -> AnyPublisher { 181 | return API.executeRequest(APIRequest: API.usersThreads.delete(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: Data.self) 182 | } 183 | 184 | public static func modify(userID: String = "me", id: String, requestBody: ThreadModifyBody) -> AnyPublisher { 185 | return API.executeRequest(APIRequest: API.usersThreads.untrash(userID: userID, id: id).request, headers: defaultHeadersWithAuth, requestBody: requestBody.dictionary, decodingType: MailThread.self) 186 | } 187 | 188 | } 189 | 190 | public class UsersSettings { 191 | 192 | public static func getAutoForwarding(userID: String = "me", id: String) -> AnyPublisher { 193 | return API.executeRequest(APIRequest: API.usersSettings.getAutoForwarding(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: AutoForwarding.self) 194 | } 195 | 196 | public static func getImap(userID: String = "me") -> AnyPublisher { 197 | return API.executeRequest(APIRequest: API.usersSettings.getImap(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: ImapSettings.self) 198 | } 199 | 200 | public static func getLanguage(userID: String = "me") -> AnyPublisher { 201 | return API.executeRequest(APIRequest: API.usersSettings.getLanguage(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: LanguageSettings.self) 202 | } 203 | 204 | public static func getPop(userID: String = "me") -> AnyPublisher { 205 | return API.executeRequest(APIRequest: API.usersSettings.getPop(userID: userID).request, headers: defaultHeadersWithAuth, requestBody: nil, decodingType: PopSettings.self) 206 | } 207 | 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Sources/Smail/ResourceTypes/Draft.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Draft.swift 3 | // gmail-client 4 | // 5 | // Created by Praneet S on 09/04/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Draft : Codable { 11 | public let id: String? 12 | public let message: Message? 13 | public init(id: String?, message: Message) { 14 | self.id = id 15 | self.message = message 16 | } 17 | } 18 | 19 | public struct DraftList : Codable { 20 | public let drafts: [Draft]? 21 | public let nextPageToken: String? 22 | public let resultSizeEstimate: Int? 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Smail/ResourceTypes/Labels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Labels.swift 3 | // gmail-client 4 | // 5 | // Created by Praneet S on 09/04/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Color : Codable { 11 | public let textColor: String? 12 | public let backgroundColor: String? 13 | } 14 | 15 | public struct Label : Codable { 16 | public let id: String? 17 | public let name: String? 18 | public let messageListVisibility: MessageListVisibility? 19 | public let labelListVisibility: LabelListVisibility? 20 | public let type: Type_? 21 | public let messagesTotal: Int? 22 | public let messagesUnread: Int? 23 | public let threadsTotal: Int? 24 | public let threadsUnread: Int? 25 | public let color: Color? 26 | 27 | public enum MessageListVisibility : String, Codable { 28 | case SHOW 29 | case HIDE 30 | } 31 | 32 | public enum LabelListVisibility : String, Codable { 33 | case LABEL_SHOW 34 | case LABEL_SHOW_IF_UNREAD 35 | case LABEL_HIDE 36 | } 37 | 38 | public enum Type_ : String, Codable { 39 | case SYSTEM 40 | case USER 41 | } 42 | } 43 | 44 | public struct LabelList : Codable { 45 | public let labels: [Label]? 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Sources/Smail/ResourceTypes/Message.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message.swift 3 | // gmail-client 4 | // 5 | // Created by Praneet S on 09/04/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Header : Codable { 11 | public let name: String? 12 | public let value: String? 13 | } 14 | 15 | public struct MessagePartBody : Codable { 16 | public let attachmentId: String? 17 | public let size: Int? 18 | public let data: String? 19 | } 20 | 21 | public struct MessagePart : Codable { 22 | public let partId: String? 23 | public let mimeType: String? 24 | public let filename: String? 25 | public let headers: [Header]? 26 | public let body: MessagePartBody? 27 | public let parts: [MessagePart]? 28 | } 29 | 30 | public struct Message : Codable { 31 | public let id: String? 32 | public let threadId: String? 33 | public let labelIds: [String]? 34 | public let snippet: String? 35 | public let historyId: String? 36 | public let internalDate: String? 37 | public let payload: MessagePart? 38 | public let sizeEstimate: Int? 39 | public let raw: String? 40 | public init(id: String?, threadID: String?, labelIDs: [String]?, snippet: String?, 41 | historyID: String?, internalDate: String?, payload: MessagePart?, 42 | sizeEstimate: Int?, raw: String?) { 43 | self.id = id 44 | self.threadId = threadID 45 | self.labelIds = labelIDs 46 | self.snippet = snippet 47 | self.historyId = historyID 48 | self.internalDate = internalDate 49 | self.payload = payload 50 | self.sizeEstimate = sizeEstimate 51 | self.raw = raw 52 | } 53 | } 54 | 55 | public struct MessagesList : Codable { 56 | public let messages: [MessageListInstance]? 57 | public let nextPageToken: String? 58 | public let resultSizeEstimate: Int? 59 | } 60 | 61 | public struct MessageListInstance : Codable { 62 | public let id: String? 63 | public let threadId: String? 64 | } 65 | 66 | public struct UserMessagesBatchModifyBody : Codable { 67 | public let ids: [String]? 68 | public let addLabelIds: [String]? 69 | public let removeLabelIds: [String]? 70 | } 71 | 72 | public struct ModifiedMessageBody : Codable { 73 | public let addLabelIds: [String]? 74 | public let removeLabelIds: [String]? 75 | } 76 | -------------------------------------------------------------------------------- /Sources/Smail/ResourceTypes/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // gmail-client 4 | // 5 | // Created by Meghana Khuntia on 10/04/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct AutoForwarding : Codable { 11 | public let enabled: Bool 12 | public let emailAddress: String 13 | public let disposition: Disposition 14 | } 15 | 16 | public struct ImapSettings : Codable { 17 | public let enabled: Bool 18 | public let autoExpunge: Bool 19 | public let expungeBehavior: ExpungeBehavior 20 | public let maxFolderSize: Int 21 | 22 | public enum ExpungeBehavior : String, Codable { 23 | case EXPUNGE_BEHAVIOR_UNSPECIFIED 24 | case ARCHIVE 25 | case TRASH 26 | case DELETE_FOREVER 27 | } 28 | } 29 | 30 | public struct LanguageSettings: Codable { 31 | public let displayLanguage: String 32 | } 33 | 34 | public struct PopSettings: Codable { 35 | public let accessWindow: AccessWindow 36 | public let disposition: Disposition 37 | 38 | public enum AccessWindow: String, Codable { 39 | case ACCESS_WINDOW_UNSPECIFIED 40 | case DISABLED 41 | case FROM_NOW_ON 42 | case ALL_MAIL 43 | } 44 | } 45 | 46 | public enum Disposition : String, Codable { 47 | case DISPOSITION_UNSPECIFIED 48 | case LEAVE_IN_INBOX 49 | case ARCHIVE 50 | case TRASH 51 | case MARK_READ 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Smail/ResourceTypes/Thread.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Thread.swift 3 | // gmail-client 4 | // 5 | // Created by Praneet S on 09/04/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct MailThread : Codable { 11 | public let id: String? 12 | public let snippet: String? 13 | public let historyId: String? 14 | public let messages: [Message]? 15 | } 16 | 17 | public struct ThreadList : Codable { 18 | public let threads: [MailThread]? 19 | public let nextPageToken: String? 20 | public let resultSizeEstimate: Int? 21 | } 22 | 23 | public struct ThreadModifyBody : Codable { 24 | public let addLabelIds: [String]? 25 | public let removeLabelIds: [String]? 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Smail/Services/REST-API.swift: -------------------------------------------------------------------------------- 1 | // 2 | // REST-API.swift 3 | // gmail-client 4 | // 5 | // Created by Praneet S on 08/04/21. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | 11 | public enum HTTPMethod: String { 12 | case GET 13 | case PUT 14 | case POST 15 | case PATCH 16 | case DELETE 17 | } 18 | 19 | public class API { 20 | 21 | public struct Request { 22 | let requestURL: String 23 | let requestMethod: HTTPMethod 24 | } 25 | 26 | public static var baseURL = "https://gmail.googleapis.com" 27 | 28 | public static func executeRequest(APIRequest: Request, headers: [String : String]?, requestBody: [String : Any]?, decodingType: T.Type) -> AnyPublisher where T: Decodable { 29 | 30 | let apiRequestURL = URL(string: API.baseURL + APIRequest.requestURL) 31 | if apiRequestURL == nil { 32 | return Fail(error: URLError(.badURL)).eraseToAnyPublisher() 33 | } 34 | 35 | var request = URLRequest(url: apiRequestURL!) 36 | request.httpMethod = APIRequest.requestMethod.rawValue 37 | if let requestBody = requestBody { 38 | let jsonData = try? JSONSerialization.data(withJSONObject: requestBody) 39 | request.httpBody = jsonData 40 | } 41 | request.allHTTPHeaderFields = headers 42 | 43 | let publisher = URLSession.shared.dataTaskPublisher(for: request) 44 | .map { $0.data } 45 | .decode(type: T.self, decoder: JSONDecoder()) 46 | .eraseToAnyPublisher() 47 | 48 | return publisher 49 | } 50 | 51 | public static func executeRequest(APIRequest: Request, headers: [String : String]?, requestBody: [String : Any]?, decodingType: T.Type) async -> T? where T: Decodable { 52 | 53 | let apiRequestURL = URL(string: API.baseURL + APIRequest.requestURL) 54 | if apiRequestURL == nil { 55 | return nil 56 | } 57 | 58 | var request = URLRequest(url: apiRequestURL!) 59 | request.httpMethod = APIRequest.requestMethod.rawValue 60 | if let requestBody = requestBody { 61 | let jsonData = try? JSONSerialization.data(withJSONObject: requestBody) 62 | request.httpBody = jsonData 63 | } 64 | request.allHTTPHeaderFields = headers 65 | 66 | do { 67 | let data = try await URLSession.shared.data(for: request) 68 | let response = try JSONDecoder().decode(T.self, from: data.0) 69 | return response 70 | } catch { 71 | // print(error) 72 | } 73 | // .map { $0.data } 74 | // .decode(type: T.self, decoder: JSONDecoder()) 75 | // .eraseToAnyPublisher() 76 | 77 | return nil 78 | } 79 | 80 | public enum resourceContentType { 81 | case Media 82 | case Metadata 83 | } 84 | 85 | public enum user { 86 | case getProfile(userID: String) 87 | case stop(userID: String) 88 | case watch(userID: String) 89 | 90 | var request: Request { 91 | switch self { 92 | case .getProfile(userID: let userID): 93 | return Request(requestURL: "/gmail/v1/users/\(userID)/profile", requestMethod: .GET) 94 | case .stop(userID: let userID): 95 | return Request(requestURL: "/gmail/v1/users/\(userID)/stop", requestMethod: .POST) 96 | case .watch(userID: let userID): 97 | return Request(requestURL: "/gmail/v1/users/\(userID)/watch", requestMethod: .POST) 98 | } 99 | } 100 | } 101 | 102 | public enum usersDrafts { 103 | case create(userId: String, type: resourceContentType) 104 | case delete(userId: String, id: String) 105 | case get(userId: String, id: String) 106 | case list(userId: String) 107 | case send(userId: String, type: resourceContentType) 108 | case update(userId: String, id: String, type: resourceContentType) 109 | 110 | var request: Request { 111 | switch self { 112 | case .create(userId: let userId, type: let type): 113 | switch type { 114 | case .Media: 115 | return Request(requestURL: "/upload/gmail/v1/users/\(userId)/drafts", requestMethod: .POST) 116 | case .Metadata: 117 | return Request(requestURL: "/gmail/v1/users/\(userId)/drafts", requestMethod: .POST) 118 | } 119 | case .delete(userId: let userId, id: let id): 120 | return Request(requestURL: "/gmail/v1/users/\(userId)/drafts/\(id)", requestMethod: .DELETE) 121 | case .get(userId: let userId, id: let id): 122 | return Request(requestURL: "/gmail/v1/users/\(userId)/drafts/\(id)", requestMethod: .GET) 123 | case .list(userId: let userId): 124 | return Request(requestURL: "/gmail/v1/users/\(userId)/drafts", requestMethod: .GET) 125 | case .send(userId: let userId, type: let type): 126 | switch type { 127 | case .Media: 128 | return Request(requestURL: "/upload/gmail/v1/users/\(userId)/drafts/send", requestMethod: .POST) 129 | case .Metadata: 130 | return Request(requestURL: "/gmail/v1/users/\(userId)/drafts/send", requestMethod: .POST) 131 | } 132 | case .update(userId: let userId, id: let id, type: let type): 133 | switch type { 134 | case .Media: 135 | return Request(requestURL: "/upload/gmail/v1/users/\(userId)/drafts/\(id)", requestMethod: .PUT) 136 | case .Metadata: 137 | return Request(requestURL: "/gmail/v1/users/\(userId)/drafts/\(id)", requestMethod: .PUT) 138 | } 139 | } 140 | } 141 | } 142 | 143 | public enum usersHistory { 144 | case list(userID: String, startHistoryId: String) 145 | 146 | var request: Request { 147 | switch self { 148 | case .list(userID: let userID, startHistoryId: let startHistoryId): 149 | return Request(requestURL: "/gmail/v1/users/\(userID)/history?startHistoryId=\(startHistoryId)", requestMethod: .GET) 150 | } 151 | } 152 | } 153 | 154 | public enum usersLabels { 155 | case create(userId: String) 156 | case delete(userId: String, id: String) 157 | case get(userId: String, id: String) 158 | case list(userId: String) 159 | case patch(userId: String, id: String) 160 | case update(userId: String, id: String) 161 | 162 | var request: Request { 163 | switch self { 164 | case .create(userId: let userId): 165 | return Request(requestURL:"/gmail/v1/users/\(userId)/labels", requestMethod: .POST) 166 | case .delete(userId: let userId, id: let id): 167 | return Request(requestURL:"/gmail/v1/users/\(userId)/labels/\(id)", requestMethod: .DELETE) 168 | case .get(userId: let userId, id: let id): 169 | return Request(requestURL:"/gmail/v1/users/\(userId)/labels/\(id)", requestMethod: .GET) 170 | case .list(userId: let userId): 171 | return Request(requestURL:"/gmail/v1/users/\(userId)/labels", requestMethod: .GET) 172 | case .patch(userId: let userId, id: let id): 173 | return Request(requestURL:"/gmail/v1/users/\(userId)/labels/\(id)", requestMethod: .PATCH) 174 | case .update(userId: let userId, id: let id): 175 | return Request(requestURL:"/gmail/v1/users/\(userId)/labels/\(id)", requestMethod: .PUT) 176 | } 177 | } 178 | } 179 | 180 | public enum usersMessages { 181 | case batchDelete(userID: String) 182 | case batchModify(userID: String) 183 | case delete(userID: String, id: String) 184 | case get(userID: String, id: String) 185 | case importMessages(userID: String, importType: resourceContentType) 186 | case insert(userID: String, importType: resourceContentType) 187 | case list(userID: String) 188 | case modify(userID: String, id: String) 189 | case send(userID: String, importType: resourceContentType) 190 | case trash(userID: String, id: String) 191 | case untrash(userID: String, id: String) 192 | 193 | var request: Request { 194 | switch self { 195 | case .batchDelete(userID: let userID): 196 | return Request(requestURL: "/gmail/v1/users/\(userID)/messages/batchDelete", requestMethod: .POST) 197 | case .batchModify(userID: let userID): 198 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/batchModify", requestMethod: .POST) 199 | case .delete(userID: let userID, id: let id): 200 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/\(id)", requestMethod: .DELETE) 201 | case .get(userID: let userID, id: let id): 202 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/\(id)", requestMethod: .GET) 203 | case .importMessages(userID: let userID, importType: let importType): 204 | switch importType { 205 | case .Media: 206 | return Request(requestURL:"/upload/gmail/v1/users/\(userID)/messages/import", requestMethod: .POST) 207 | case .Metadata: 208 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/import", requestMethod: .POST) 209 | } 210 | case .insert(userID: let userID, importType: let importType): 211 | switch importType { 212 | case .Media: 213 | return Request(requestURL:"/upload/gmail/v1/users/\(userID)/messages", requestMethod: .POST) 214 | case .Metadata: 215 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages", requestMethod: .POST) 216 | } 217 | case .list(userID: let userID): 218 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages", requestMethod: .GET) 219 | case .modify(userID: let userID, id: let id): 220 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/\(id)/modify", requestMethod: .POST) 221 | case .send(userID: let userID, importType: let importType): 222 | switch importType { 223 | case .Media: 224 | return Request(requestURL:"/upload/gmail/v1/users/\(userID)/messages/send", requestMethod: .POST) 225 | case .Metadata: 226 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/send", requestMethod: .POST) 227 | } 228 | case .trash(userID: let userID, id: let id): 229 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/\(id)/trash", requestMethod: .POST) 230 | case .untrash(userID: let userID, id: let id): 231 | return Request(requestURL:"/gmail/v1/users/\(userID)/messages/\(id)/untrash", requestMethod: .POST) 232 | } 233 | } 234 | } 235 | 236 | public enum messageAttachments { 237 | case get(userID: String, messageID: String, id: String) 238 | 239 | var request: String { 240 | switch self { 241 | case .get(userID: let userID, messageID: let messageID, id: let id): 242 | return "/gmail/v1/users/\(userID)/messages/\(messageID)/attachments/\(id)" 243 | } 244 | } 245 | } 246 | 247 | public enum usersSettings { 248 | case getAutoForwarding(userID: String) 249 | case getImap(userID: String) 250 | case getLanguage(userID: String) 251 | case getPop(userID: String) 252 | case getVacation(userID: String) 253 | case updateAutoForwarding(userID: String) 254 | case updateImap(userID: String) 255 | case updateLanguage(userID: String) 256 | case updatePop(userID: String) 257 | case updateVacation(userID: String) 258 | 259 | var request: Request { 260 | switch self { 261 | case .getAutoForwarding(userID: let userID): 262 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/autoForwarding", requestMethod: .GET) 263 | case .getImap(userID: let userID): 264 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/imap", requestMethod: .GET) 265 | case .getLanguage(userID: let userID): 266 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/language", requestMethod: .GET) 267 | case .getPop(userID: let userID): 268 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/pop", requestMethod: .GET) 269 | case .getVacation(userID: let userID): 270 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/vacation", requestMethod: .GET) 271 | case .updateAutoForwarding(userID: let userID): 272 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/autoForwarding", requestMethod: .PUT) 273 | case .updateImap(userID: let userID): 274 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/imap", requestMethod: .PUT) 275 | case .updateLanguage(userID: let userID): 276 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/language", requestMethod: .PUT) 277 | case .updatePop(userID: let userID): 278 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/pop", requestMethod: .PUT) 279 | case .updateVacation(userID: let userID): 280 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/vacation", requestMethod: .PUT) 281 | } 282 | } 283 | } 284 | 285 | public enum usersSettingsDelegates { 286 | case create(userID: String) 287 | case delete(userID: String, delegateEmail: String) 288 | case get(userID: String, delegateEmail: String) 289 | case list(userID: String) 290 | 291 | var request: Request { 292 | switch self { 293 | case .create(userID: let userID): 294 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/delegates", requestMethod: .POST) 295 | case .delete(userID: let userID, delegateEmail: let delegateEmail): 296 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/delegates/\(delegateEmail)", requestMethod: .DELETE) 297 | case .get(userID: let userID, delegateEmail: let delegateEmail): 298 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/delegates/\(delegateEmail)", requestMethod: .GET) 299 | case .list(userID: let userID): 300 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/delegates", requestMethod: .GET) 301 | } 302 | } 303 | } 304 | 305 | public enum usersSettingsFilters { 306 | case create(userID: String) 307 | case delete(userID: String, id: String) 308 | case get(userID: String, id: String) 309 | case list(userID: String) 310 | 311 | var request: Request { 312 | switch self { 313 | case .create(userID: let userID): 314 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/filters", requestMethod: .POST) 315 | case .delete(userID: let userID, id: let id): 316 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/filters/\(id)", requestMethod: .DELETE) 317 | case .get(userID: let userID, id: let id): 318 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/filters/\(id)", requestMethod: .GET) 319 | case .list(userID: let userID): 320 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/filters", requestMethod: .GET) 321 | } 322 | } 323 | } 324 | 325 | public enum usersSettingsForwardingAddresses { 326 | case create(userID: String) 327 | case delete(userID: String, forwardingEmail: String) 328 | case get(userID: String, forwardingEmail: String) 329 | case list(userID: String) 330 | 331 | var request: Request { 332 | switch self { 333 | case .create(userID: let userID): 334 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/forwardingAddresses", requestMethod: .POST) 335 | case .delete(userID: let userID, forwardingEmail: let forwardingEmail): 336 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/forwardingAddresses/\(forwardingEmail)", requestMethod: .DELETE) 337 | case .get(userID: let userID, forwardingEmail: let forwardingEmail): 338 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/forwardingAddresses/\(forwardingEmail)", requestMethod: .GET) 339 | case .list(userID: let userID): 340 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/forwardingAddresses", requestMethod: .GET) 341 | } 342 | } 343 | } 344 | 345 | public enum usersSettingsSendAs { 346 | case create(userID: String) 347 | case delete(userID: String, sendAsEmail: String) 348 | case get(userID: String, sendAsEmail: String) 349 | case list(userID: String) 350 | case patch(userID: String, sendAsEmail: String) 351 | case update(userID: String, sendAsEmail: String) 352 | case verify(userID: String, sendAsEmail: String) 353 | 354 | var request: Request { 355 | switch self { 356 | case .create(userID: let userID): 357 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs", requestMethod: .POST) 358 | case .delete(userID: let userID, sendAsEmail: let sendAsEmail): 359 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)", requestMethod: .DELETE) 360 | case .get(userID: let userID, sendAsEmail: let sendAsEmail): 361 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)", requestMethod: .GET) 362 | case .list(userID: let userID): 363 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs", requestMethod: .GET) 364 | case .patch(userID: let userID, sendAsEmail: let sendAsEmail): 365 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)", requestMethod: .PATCH) 366 | case .update(userID: let userID, sendAsEmail: let sendAsEmail): 367 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)", requestMethod: .PUT) 368 | case .verify(userID: let userID, sendAsEmail: let sendAsEmail): 369 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)/verify", requestMethod: .POST) 370 | } 371 | } 372 | } 373 | 374 | public enum usersSettingsSendAsSmimeInfo { 375 | case delete(userID: String, sendAsEmail: String, id: String) 376 | case get(userID: String, sendAsEmail: String, id: String) 377 | case insert(userID: String, sendAsEmail: String) 378 | case list(userID: String, sendAsEmail: String) 379 | case setDefault(userID: String, sendAsEmail: String, id: String) 380 | 381 | var request: Request { 382 | switch self { 383 | case .delete(userID: let userID, sendAsEmail: let sendAsEmail, id: let id): 384 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)/smimeInfo/\(id)", requestMethod: .DELETE) 385 | case .get(userID: let userID, sendAsEmail: let sendAsEmail, id: let id): 386 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)/smimeInfo/\(id)", requestMethod: .GET) 387 | case .insert(userID: let userID, sendAsEmail: let sendAsEmail): 388 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)/smimeInfo", requestMethod: .POST) 389 | case .list(userID: let userID, sendAsEmail: let sendAsEmail): 390 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)/smimeInfo", requestMethod: .GET) 391 | case .setDefault(userID: let userID, sendAsEmail: let sendAsEmail, id: let id): 392 | return Request(requestURL: "/gmail/v1/users/\(userID)/settings/sendAs/\(sendAsEmail)/smimeInfo/\(id)/setDefault", requestMethod: .POST) 393 | } 394 | } 395 | } 396 | 397 | public enum usersThreads { 398 | case delete(userID: String, id: String) 399 | case get(userID: String, id: String) 400 | case list(userID: String, maxResults: Int?, pageToken: String?, query: String?, labelIDs: String?, includeSpamTrash: Bool?) 401 | case modify(userID: String, id: String) 402 | case trash(userID: String, id: String) 403 | case untrash(userID: String, id: String) 404 | 405 | var request: Request { 406 | switch self { 407 | case .delete(userID: let userID, id: let id): 408 | return Request(requestURL: "/gmail/v1/users/\(userID)/threads/\(id)", requestMethod: .DELETE) 409 | case .get(userID: let userID, id: let id): 410 | return Request(requestURL: "/gmail/v1/users/\(userID)/threads/\(id)", requestMethod: .GET) 411 | case .list(userID: let userID, maxResults: let maxResults, pageToken: let pageToken, query: let query, labelIDs: let labelIDs, includeSpamTrash: let includeSpamTrash): 412 | var url: String = "/gmail/v1/users/\(userID)/threads" 413 | ["maxResults", "pageToken", "q", "labelIDs", "includeSpamTrash"].forEach({ queryParam in 414 | switch queryParam { 415 | case "maxResults": 416 | if let maxResults = maxResults { 417 | url = url.appendQueryParam(queryParam: queryParam, value: maxResults) 418 | } 419 | case "pageToken": 420 | if let pageToken = pageToken { 421 | url = url.appendQueryParam(queryParam: queryParam, value: pageToken) 422 | } 423 | case "q": 424 | if let query = query { 425 | url = url.appendQueryParam(queryParam: queryParam, value: query) 426 | } 427 | case "labelIDs": 428 | if let labelIDs = labelIDs { 429 | url = url.appendQueryParam(queryParam: queryParam, value: labelIDs) 430 | } 431 | case "includeSpamTrash": 432 | if let includeSpamTrash = includeSpamTrash { 433 | url = url.appendQueryParam(queryParam: queryParam, value: includeSpamTrash) 434 | } 435 | default: 436 | break 437 | } 438 | }) 439 | // print(url) 440 | return Request(requestURL: url, requestMethod: .GET) 441 | case .modify(userID: let userID, id: let id): 442 | return Request(requestURL: "/gmail/v1/users/\(userID)/threads/\(id)/modify", requestMethod: .POST) 443 | case .trash(userID: let userID, id: let id): 444 | return Request(requestURL: "/gmail/v1/users/\(userID)/threads/\(id)/trash", requestMethod: .POST) 445 | case .untrash(userID: let userID, id: let id): 446 | return Request(requestURL: "/gmail/v1/users/\(userID)/threads/\(id)/untrash", requestMethod: .POST) 447 | } 448 | } 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /Sources/Smail/Smail.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | 4 | public class Smail : ObservableObject { 5 | 6 | private var mailID: String? 7 | 8 | private var refreshInterval: Int? 9 | 10 | @Published public var userMessages: MessagesList? 11 | @Published public var userThreads: ThreadList? 12 | @Published public var userLabels: LabelList? 13 | @Published public var userDrafts: DraftList? 14 | 15 | public var cancellables: Set = [] 16 | 17 | public init() { 18 | 19 | } 20 | 21 | public func authenticate(authToken: String, mailID: String, refreshInterval: Int?) { 22 | Gmail.setAuth(bearerToken: authToken) 23 | self.mailID = mailID 24 | self.refreshInterval = refreshInterval ?? -1 25 | } 26 | 27 | public func draftCompose(draft: Draft, type: API.resourceContentType) -> AnyPublisher { 28 | Gmail.UsersDrafts.create(userID: "me", type: type, draft: draft.dictionary ?? ["message":["raw":""]]) 29 | .receive(on: DispatchQueue.main) 30 | .decode(type: Draft.self, decoder: JSONDecoder()) 31 | .eraseToAnyPublisher() 32 | } 33 | 34 | public func fetchUserThreads(maxResults: Int? = nil, pageToken: String? = nil, query: String? = nil, labelIDs: String? = nil, includeSpamTrash: Bool? = nil) { 35 | Gmail.UsersThreads.list(userID: self.mailID!, maxResults: maxResults, pageToken: pageToken, query: query, labelIDs: labelIDs, includeSpamTrash: includeSpamTrash) 36 | .receive(on: DispatchQueue.main) 37 | .sink(receiveCompletion: { completion in 38 | // print(completion) 39 | }, receiveValue: { threads in 40 | self.userThreads = threads 41 | // print(threads) 42 | }) 43 | .store(in: &cancellables) 44 | } 45 | 46 | public func fetchUserLabels() { 47 | Gmail.UsersLabels.list(userID: self.mailID!) 48 | .receive(on: DispatchQueue.main) 49 | .sink(receiveCompletion: { completion in 50 | // print(completion) 51 | }, receiveValue: { labels in 52 | self.userLabels = labels 53 | // print(labels) 54 | }) 55 | .store(in: &cancellables) 56 | } 57 | 58 | public func fetchUserDrafts() { 59 | Gmail.UsersDrafts.list(userID: self.mailID!) 60 | .receive(on: DispatchQueue.main) 61 | .sink(receiveCompletion: { completion in 62 | // print(completion) 63 | }, receiveValue: { drafts in 64 | self.userDrafts = drafts 65 | // print(drafts) 66 | }) 67 | .store(in: &cancellables) 68 | } 69 | 70 | public func fetchUserMessages() { 71 | Gmail.UsersMessages.list(userID: self.mailID!) 72 | .receive(on: DispatchQueue.main) 73 | .sink(receiveCompletion: { completion in 74 | // print(completion) 75 | }, receiveValue: { messages in 76 | self.userMessages = messages 77 | // print(messages) 78 | }) 79 | .store(in: &cancellables) 80 | } 81 | 82 | public func fetchUserThreads(maxResults: Int? = nil, pageToken: String? = nil, query: String? = nil, labelIDs: String? = nil, includeSpamTrash: Bool? = nil) async { 83 | self.userThreads = await Gmail.UsersThreads.list(userID: self.mailID!, maxResults: maxResults, pageToken: pageToken, query: query, labelIDs: labelIDs, includeSpamTrash: includeSpamTrash) 84 | } 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Sources/Smail/Utils/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // Smail 4 | // 5 | // Created by Praneet S on 13/05/21. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Encodable { 11 | public var dictionary: [String: Any]? { 12 | guard let data = try? JSONEncoder().encode(self) else { return nil } 13 | return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] } 14 | } 15 | } 16 | 17 | extension String { 18 | public func appendQueryParam(queryParam: String, value: Any) -> String { 19 | var updatedURL: String = self 20 | if self.contains("?") { 21 | updatedURL.append("&") 22 | } else { 23 | updatedURL.append("?") 24 | } 25 | updatedURL.append("\(queryParam)=\(value)") 26 | return updatedURL 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SmailTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SmailTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SmailTests/SmailTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Smail 3 | 4 | final class SmailTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(Smail().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/SmailTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SmailTests.allTests), 7 | ] 8 | } 9 | #endif 10 | --------------------------------------------------------------------------------