Cross-platform Google APIs for Swift built on Codable & NIO
...
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/adiwajshing/Queenfisher.git", from: "0.1.0")
],
targets: [
.target(name: "MyTarget", dependencies: ["Queenfisher", ...])
]
...
import Queenfisher
import Queenfisher
import NIO
let pathToSecret = URL(fileURLWithPath: "Path/to/client_secret.json")
let pathToToken = URL(fileURLWithPath: "Path/to/my_token.json") // place to save the generated token
let client: GoogleOAuthClient = try .loading(from: pathToSecret)
// generate the authentication url where you can sign in & get your access token
let authUrl = client.authUrl(for: .mailAll + .sheets) // authenticate for full access to mail & spreadsheets
print ("sign in here & paste the code from the link below: \(authUrl)") // open the url in a browser
/*
Once you sign off on the permissions, google will redirect you to the url you specified in the client secret
If you don't have a server listening, you can just extract the code & paste it here, and you will get your access & refresh tokens
The code will be in the url query like: http://localhost:8080?code=abcdefg&scope=blahblah
Paste `abcdefg` below
*/
let code = readLine(strippingNewline: true)!
let accessToken = try client.fetchToken(fromCode: code).wait() // will exchange code for access & refresh tokens
print("got access token: \($0)")
/* You can now use this access token for sheets or gmail */
try JSONEncoder().encode(accessToken).write(to: pathToToken) // save the token as a JSON
// get your client secret
let client: GoogleOAuthClient = try .loading(from: pathToSecret)
// create an authentication factory using the access token & secret
let authFactory = try client.factory(usingAccessToken: .loading(fromJSONAt: pathToToken))
/*
Use authFactory as your access mediator when accessing APIs.
This will ensure you always have an active access token
*/
import Queenfisher
let pathToAcc = URL(fileURLWithPath: "Path/to/service_account.json")
let serviceAcc: GoogleServiceAccount = try .loading(fromJSONAt: pathToAcc)
let authFactory = serviceAcc.factory (forScope: .sheets) // get authentication for sheets
/*
Use authFactory as your access mediator when accessing APIs.
This will ensure you always have an active access token
*/
import Queenfisher
import Promises
// create an authentication factory using the access token & secret
// make sure your token has access to GMail
// do note, service accounts cannot access GMail unless with GSuite accounts
let client: GoogleOAuthClient = try .loading(from: pathToSecret)
let authFactory = try client.factory(usingAccessToken: .loading(fromJSONAt: pathToToken))
let gmail: GMail = .init(using: authFactory)
let profile = try gmail.profile().wait()
print ("Oh hello: \(profile.emailAddress)") // print email address
gmail.list() // lists all messages in inbox, sent & drafts ordered by timestamp
.map {
print ("got \($0.resultSizeEstimate) messages")
if let messages = $0.messages {
for m in messages { // metadata of messages
print ("id: \(m.id)")
}
}
}
You can refine your search by specifying query parameters mentioned here. For example:gmail.list(q: "is:unread") // lists all unread messages
gmail.list(q: "subject:permission") // subject contains the word `permission`
gmail.list(q: "from:[email protected]") // all emails from this email address
gmail.list() // lists all messages in inbox, sent & drafts ordered by timestamp
.flatMap { gmail.get(id: $0.messages![0].id, format: .full) } // get the first email received
.map {
print ("email from: \($0.from!)")
print ("email subject: \($0.subject!)")
print ("email snippet: \($0.snippet!)")
}
Dive deeper into the GMail.Message class to get the attachements & the entire text of the email.let attachFile = URL(fileURLWithPath: "Path/to/fave_image.jpeg")
let mail: GMail.Message = .init(to: [ .namedEmail("Myself & I", profile.emailAddress) ],
subject: "Hello",
text: "My name <b>Jeff</b>.",
attachments: [ try! .attachment(fileAt: attachFile) ])
gmail.send (message: mail)
.whenComplete { print ("yay sent mail with ID: \($0.id)") }
.whenFailure { print ("error in sending: \($0)") }
The text
in emails must be some html text.let profile = try await(gmail.profile()) // get profile
gmail.list()
.flatMap { gmail.get(id: $0.messages![0].id, format: .full) } // get the first email received
.flatMap { message -> EventLoopFuture<GMail.Message> in
let isMailFromMe = $0.from!.email == profile.emailAddress // determine if the email was sent by me
let reply: GMail.Message = GMail.Message(replyingTo: message,
fromMe: isMailFromMe,
text: "Wow this is a reply")!
return gmail.send (message: reply)
}
.whenComplete { print ("yay sent reply with ID: \($0.id)") }
// fetch unread emails every 60 seconds
// note: once a mail is forwarded to this handler, it will not be forwarded again in the future
gmail.fetch(over: .seconds(60), q: "is:unread") { result in
switch result {
case .success(let messages):
print ("got \(messages.count) new messages")
break
case .failure(let error):
print("Oh no, got an error: \(error)")
break
}
}
gmail.markRead (id: idOfTheMessage)
.whenComplete { print ("yay read mail with ID: \($0.id)") }
gmail.trash (id: idOfTheMessage)
.whenComplete { print ("yay trashed mail with ID: \($0.id)") }
gmail.modify (id: idOfTheMessage, adddingLabelIds: ["UNREAD"]) // effectively mark an email as unread
.whenComplete { print ("yay modified mail with ID: \($0.id)") }
import Queenfisher
import NIO
// create an authentication factory using the access token & secret
// make sure your token has access to GMail
// do note, service accounts cannot access GMail unless with GSuite accounts
let client: GoogleOAuthClient = try .loading(from: pathToSecret)
let authFactory = try client.factory(usingAccessToken: .loading(fromJSONAt: pathToToken))
let spreadsheetId = "abcdefghi" // insert actual spreadsheet ID
let spreadsheet: Spreadsheet = try .get(spreadsheetId, using: authFactory).wait ()
print("Got spreadsheet '\(spreadsheet.properties.title)', sheets: \(spreadsheet.sheets.map({$0.properties.title}))")
// get the sheet ID, it's the unique ID for every sheet, you'll need it for almost all operations
let sheetId = spreadsheet.sheet (forTitle: "Sheet 1")!.properties.sheetId!
let rows = [
["hello", "this", "is", "jeff"],
["yes", "my", "name", "jeff"],
["of course", "this", "is", "jeff"]
]
// write these rows to the start of the spreadsheet
spreadsheet.writeRows (sheetId: sheetId, rows: rows, starting: .cell(0,0))
.whenComplete { _ in print ("yay done") }
// get the sheet ID, it's the unique ID for every sheet, you'll need it for almost all operations
let sheetId = spreadsheet.sheet (forTitle: "Sheet 1")!.properties.sheetId!
let rows = [
["wow", "more", "rows", "!"],
["yes", "this", "is", "great"]
]
// append these rows after the last row with data in the sheet
spreadsheet.appendRows (sheetId: sheetId, rows: rows)
.whenComplete { _ in print ("yay done") }
let sheetId = spreadsheet.sheet (forTitle: "Sheet 1")!.properties.sheetId!
spreadsheet.read (sheetId: sheetId)
.whenComplete { print ("\($0.values)") }
/* or if you want to read a specific range */
spreadsheet.read (sheetId: sheetId, range: (.row(1), .row(5))) // read all columns in row index 1 to 5
.whenComplete { print ("\($0.values)") }
spreadsheet.insert(sheetId: sheetId, range: 2..<4, dimension: .columns) // insert 2 columns at index 2
.whenComplete { _ in print ("yay inserted") }
spreadsheet.append(sheetId: sheetId, size: 3, dimension: .columns) // append 3 columns
.whenComplete { _ in print ("yay appended") }
spreadsheet.move(sheetId: sheetId, range: 2..<3, to: 2, to: 1, dimension: .rows) // move rows 2-3 to index 1
.whenComplete { _ in print ("yay moved") }
spreadsheet.delete(sheetId: sheetId, range: 2..<3, to: 2, dimension: .rows) // deletes rows at indexes 2-3
.whenComplete { _ in print ("yay deleted") }
spreadsheet.create(title: "Name of the sheet", dimensions: .init(rowCount: 10, columnCount: 5))
.whenComplete { print ("yay created with ID: \($0.replies.first!.addSheet!.properties!.sheetId)") }
spreadsheet.delete(sheetId: sheetId)
.whenComplete { _ in print ("yay deleted") }
spreadsheet.clear(sheetId: sheetId) // will delete all data in the sheet
.whenComplete { _ in print ("yay cleared") }
Haven’t documented IndexedSheet & AtomicSheet yet 😕