2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【iOS】Gmail APIでメール情報を取得

Last updated at Posted at 2021-03-29

目的と手順の説明

今回は、私の最新のGmailを誰から受信したのか?を取得することを目的にするよ〜〜〜〜〜。

1.Gmail APIを有効にする

2.認証画面を作成

3.OAuth2.0を利用して、クライアントアプリにトークンを受けとる

4.Gmail APIを叩く

下の記事を読んでから、この記事を読み進めることをおすすめします。
一番分かりやすい OAuth の説明

1.Gmail APIを有効にする

(もしかしたら、1より先に2をしなあかんかったかもしれん。。。)
Gmail APIを有効
アプリを公開しないなら、全てを記述する必要がないので5分ぐらいで終わります。

認証情報の隣にある認証情報を作成というボタンをクリックしてOuthクライアントIDを選択する
→アプリケーションの種類をiOSにする→バンドルIDはXcodeで作成したアプリのBoundleIdentifierをコピペする。

2.認証画面を作成

認証画面は、クライアントアプリがアクセストークンを作成する時に、エンドユーザーに対して認可する権限を与える画面です。この画面自体プログラマーが作る必要がありません。もうすでに用意されている。
認証画面の途中でスコープを聞かれると思います。そのとき、__制限付きのスコープのhttps://mail.google.com/__を選択する。(今回はメール情報を取得するから)
テストユーザーも追加しておく

これでサーバーサイドの設定はしゅーーーりょーーーーーーーーう!!
これから、Xcodeで作業します。

3.OAuth2.0を利用して、クライアントアプリにトークンを受けとる

OAuthを利用する準備

この記事みながらセットアップした。
ライブラリのインストールにCocoaPodsを使用します。
ターミナルを開けて〜〜〜〜〜

cd アプリのディレクトリ

アプリのディレクトリに移動します。

 pod init

ポッドファイルを作成。

    pod 'GoogleAPIClientForREST/Gmail', '~> 1.3.0'
    pod 'GTMAppAuth'

podファイルに上記を追加して

pod update

アップデートしたら、大丈夫だぁ〜〜〜〜〜

アクセストークンをリクエスト

今回使う変数を全て定義するぞ〜
その前に、したのインポートを忘れずに

import AppAuth
import GoogleAPIClientForREST
import GTMAppAuth
private let scopes = ["https://mail.google.com/"]
private let kClientID = "クライアントID"
private let kRedirectURL  = URL.init(string: "リダイレクトURL"+":/oauthredirect")!
private let configuration = GTMAppAuthFetcherAuthorization.configurationForGoogle()
private var authorization: GTMAppAuthFetcherAuthorization?
private let userId=Gメールアドレス

scopes

2の認証画面の設定で使用したスコープをいれる。

kClientIDとkRedirectURL

google.com/api
上のリンクから認証情報のOAuth2.0クライアントIDに追加されているアプリの編集画面に行けばみれる!
クライアントIDiOSのURLスキームあります。
kClientID=クライアントID
__kRedirectURL=iOSのURLスキーム+":/oauthredirect"__です
(:/oauthredirect"を忘れずに! 俺は忘れていた。)

認証画面を表示させる

private func showAuthorizationDialog() {
        let request = OIDAuthorizationRequest.init(configuration: configuration,
                                                   clientId: self.kClientID,
                                                   scopes: scopes,
                                                   redirectURL: kRedirectURL,
                                                   responseType: OIDResponseTypeCode,
                                                   additionalParameters: nil)
        
        let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.currentAuthorizationFlow=OIDAuthState.authState(byPresenting: request, presenting: self, callback: { (authState, err) in
            if let error = err {
                NSLog("\(error)")
            } else {
                if let authState = authState {
                    self.authorization = GTMAppAuthFetcherAuthorization.init(authState: authState)
                    GTMAppAuthFetcherAuthorization.save(self.authorization!, toKeychainForName: "authorization")
                }
            }
        })
    }

軽く説明していきます。

let request = OIDAuthorizationRequest.init(configuration: configuration,
                                                   clientId: self.kClientID,
                                                   scopes: scopes,
                                                   redirectURL: kRedirectURL,
                                                   responseType: OIDResponseTypeCode,
                                                   additionalParameters: nil)

認証画面を表示するためのサーバーに対するリクエストを表しています。

responseType:は応答のタイプを表しています。
additionalParameters:はクライアントの追加の許可パラメーターです。今回はないのでnilを渡します。

let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate

AppDelegateにアクセスしている理由は後ほどわかります。
appDelegateに変数を置いている理由は、異なるSwiftファイル間で変数を共有するためです。
AppDelegateに宣言した変数は、どこのViewControllerでも共有できる。

 appDelegate.currentAuthorizationFlow=OIDAuthState.authState(byPresenting: request, presenting: self, callback: { (authState, err) in
            if let error = err {
                NSLog("\(error)")
            } else {
           //OIDAuthStateからGTMAppAuthFetcherAuthorizationを作成します。
                if let authState = authState {
                    self.authorization = GTMAppAuthFetcherAuthorization.init(authState: authState)
                    GTMAppAuthFetcherAuthorization.save(self.authorization!, toKeychainForName: "authorization")
                }
            }
        })

上記のコードで認証要求を実行します。
appleDelegateクラスにcurrentAuthorizationFlowをOIDExternalUserAgentSession?型で宣言しておく。
より詳しく知りたいかたは下のリンクに飛べ〜〜〜〜〜〜〜〜
README.md

// Serialize to Keychain
GTMAppAuthFetcherAuthorization.save(_authorization, toKeychainForName: kGTMAppAuthExampleAuthorizerKey)

// Deserialize from Keychain
let authorization = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: kGTMAppAuthExampleAuthorizerKey)

// Remove from Keychain
GTMAppAuthFetcherAuthorization.removeFromKeychain(forName: kGTMAppAuthExampleAuthorizerKey)

一番上のコードでキーチェーンを利用して、ログイン状態を保存します。
いくつもログインしたい場合は、名前を変えて保存したらいいだけだから、すごい便利ですね。
したの記事でキーチェーンの理解をしておくといいかももも桃もお
キーチェーンの記事

ここまでで、認証完了!!

4.Gmail APIを叩く

やっと最後の章になります。長かった〜、、、、

コード全体

@objc func message(_ senfder:UIButton){
        let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)
        let service = GTLRGmailService()
        service.authorizer = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: "authorization")
        service.executeQuery( query,
                              delegate: self,
                              didFinish: #selector(callback) )
    }
 @objc func callback( ticket : GTLRServiceTicket,
                             finishedWithObject response : GTLRGmail_ListMessagesResponse,
                             error : NSError? ){
            if let error = error {
                print("メッセージリストの取得に失敗しました。")
                print(error)
                return
            }
        print("メッセージリストの取得に成功しました。")
        guard let message=response.messages?.first else {return}
        guard let messageid=message.identifier else {return}
        guard let url=URL(string: "https://gmail.googleapis.com/gmail/v1/users/\(userId)/messages/\(messageid)") else {return}
        var urlRequest=URLRequest(url: url)
        guard let token=authorization?.authState.lastTokenResponse?.accessToken else {return}
        urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        let session=URLSession(configuration: URLSessionConfiguration.default)
        let task=session.dataTask(with: urlRequest){
            (data,urlResponse,err) in
            if let err=err {
                print("失敗しました\(err)")
            }
            do{
                let responseJson=try JSONDecoder().decode(res.self, from: data!)
                responseJson.payload.headers.forEach {
                    if($0.name=="From"){
                        print($0.value)
                    }
                }
            }catch(let err){
                print("失敗した\(err)")
            }
        }.resume()
    }

コードの解説

説明ながなる〜〜〜〜〜〜〜〜疲れた〜〜〜〜〜〜〜〜

@objc func message(_ senfder:UIButton){
        let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)
        let service = GTLRGmailService()
        service.authorizer = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: "authorization")
        service.executeQuery( query,
                              delegate: self,
                              didFinish: #selector(callback) )
    }

messageメソッドはボタンが押されたら呼ばれるメソッドです。
詳しく見ていきます。

let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)

GTLRGmailQuery_UsersMessagesList.queryはユーザーのメールボックス内のメッセージを一覧表示する命令です。useIdはGmailアドレスです。

let service = GTLRGmailService()
service.authorizer = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: "authorization")

GTLRGmailServiceはGmailAPIクエリを実行するための作業人みたいな感じだと思います。
この作業する人が本人かどうか調べます。authorizerに、認証完了している情報をキーチェーンを用いて、GTMAppAuthFetcherAuthorizationを生成して代入する。これは、認証許可している人かどうか調べるためです。

 service.executeQuery( query,
                       delegate: self,
                       didFinish: #selector(callback) )

作業人が本人だったら作業開始します。
その時、callback関数の引数に結果が帰っていきます。
callbackの中身を具体的に見ていきます。

@objc func callback( ticket : GTLRServiceTicket,
                             finishedWithObject response : GTLRGmail_ListMessagesResponse,
                             error : NSError? ){
            if let error = error {
                print("メッセージリストの取得に失敗しました。")
                print(error)
                return
            }
        print("メッセージリストの取得に成功しました。")
        guard let message=response.messages?.first else {return}
        guard let messageid=message.identifier else {return}
        guard let url=URL(string: "https://gmail.googleapis.com/gmail/v1/users/\(userId)/messages/\(messageid)") else {return}
        var urlRequest=URLRequest(url: url)
        guard let token=authorization?.authState.lastTokenResponse?.accessToken else {return}
        urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        let session=URLSession.shared
        session.dataTask(with: urlRequest){
            (data,urlResponse,err) in
            if let err=err {
                print("失敗しました\(err)")
            }
            do{
                let responseJson=try JSONDecoder().decode(res.self, from: data!)
                responseJson.payload.headers.forEach {
                    if($0.name=="From"){
                        print($0.value)
                    }
                }
            }catch(let err){
                print("失敗した\(err)")
            }
        }.resume()
    }
if let error = error {
                print("メッセージリストの取得に失敗しました。")
                print(error)
                return
            }

引数のerrorに値が入っていたら、エラーを表示し、returnでcallback関数を抜けます。

guard let message=response.messages?.first else {return}
guard let messageid=message.identifier else {return}

response.messagesは,配列でいーーーっぱい、messageIDとthreadIDが格納されています
今回最初のメッセージが取得したいのでresponse.messages?.firstで配列の最初のメッセージ情報を取得します
ほんでもって,message.identifierでメッセージIDを取得します.

このメッセージIDを用いて,URLを生成します.

guard let url=URL(string: "https://gmail.googleapis.com/gmail/v1/users/\(userId)/messages/\(messageid)") else {return}
var urlRequest=URLRequest(url: url)
guard let token=authorization?.authState.lastTokenResponse?.accessToken else {return}
urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

先ほどのmessageidを用いてURLを生成します。
その後、URLRequest型を定義します。
URLRequest型は通信のリクエストを表現する型です。HTTPリクエストのURL、ヘッダ、メソッド、ボディなどの情報を持ちます。
ヘッダにはアクセストークンが必要です。
(urlRequest.httpMethod="GET"を途中に書いても問題ないと思います。)
ヘッダにアクセストークンを追加します。
forHTTPHeaderFieldについては、通信に関するお話でよくわからないです。( i _ i )
また、勉強しておきます。
Appleのドキュメントに詳しく乗っています。

let session=URLSession.shared
session.dataTask(with: urlRequest){(data,urlResponse,err) in
         if let err=err {
                print("失敗しました\(err)")
            }
         do{
            let responseJson=try JSONDecoder().decode(res.self, from: data!)
            responseJson.payload.headers.forEach {
              if($0.name=="From"){
                  print($0.value)
               }
            }
          }catch(let err){
            print("失敗した\(err)")
          }
}.resume()

URLSessionクラスは,URL経由でのデータの送受信に使います。
リクエストはタスクと呼ばれます。
詳しく知りたい方はしたの記事を参考に
【Swift】URLSessionまとめ
URLSession.sharedはURLSessionクラスにデフォルト値が設定されたインスタンスです。

if let err=err {
                print("失敗しました\(err)")
}

通信が完了して、エラーが帰ってきたらエラーを表示します

do{
    let responseJson=try JSONDecoder().decode(res.self, from: data!)
    responseJson.payload.headers.forEach {
         if($0.name=="From"){
                  print($0.value)
         }
     }
}catch(let err){
            print("失敗した\(err)")
}

通信が完了してdataの中に一番最新のメール情報に関するJSONファイルがあるのでパースします。
JsonDecoder().decode()でSwiftの型に変換しています。
今回、誰から連絡きたのかが知りたいのでforEachでループして探しています。。。
resは以下のようです。

struct res: Codable{
    var payload:Playload
    
    struct Playload:Codable {
        var headers:[Header]
    }
    struct Header:Codable {
        var name:String
        var value:String
    }
}

誰から、連絡きていたのか?を記事に載せようとしたけど、しょーもない人だったのでやめときます。。

感想

初心者なのでわからないことばかりですが、少しでもお力になれれば幸いです。

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?