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

#目的と手順の説明
今回は、私の最新のGmailを誰から受信したのか？を取得することを目的にするよ〜〜〜〜〜。
####1.Gmail APIを有効にする
####2.認証画面を作成
####3.OAuth2.0を利用して、クライアントアプリにトークンを受けとる
####4.Gmail APIを叩く



下の記事を読んでから、この記事を読み進めることをおすすめします。
[一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be)

#1.Gmail APIを有効にする
（もしかしたら、1より先に2をしなあかんかったかもしれん。。。）
[Gmail APIを有効](https://spica.tokyo/note/275)
アプリを公開しないなら、全てを記述する必要がないので5分ぐらいで終わります。

認証情報の隣にある認証情報を作成というボタンをクリックしてOuthクライアントIDを選択する
→アプリケーションの種類をiOSにする→バンドルIDはXcodeで作成したアプリのBoundleIdentifierをコピペする。
#2.認証画面を作成
認証画面は、クライアントアプリがアクセストークンを作成する時に、エンドユーザーに対して認可する権限を与える画面です。この画面自体プログラマーが作る必要がありません。もうすでに用意されている。
認証画面の途中で``スコープ``を聞かれると思います。そのとき、__制限付きのスコープのhttps://mail.google.com/__を選択する。（今回はメール情報を取得するから）
テストユーザーも追加しておく

これでサーバーサイドの設定はしゅーーーりょーーーーーーーーう！！
これから、Xcodeで作業します。
#3.OAuth2.0を利用して、クライアントアプリにトークンを受けとる
##OAuthを利用する準備
[この記事](https://qiita.com/ryokkkke/items/dd04bea2b579c16a9b19)みながらセットアップした。
ライブラリのインストールにCocoaPodsを使用します。
ターミナルを開けて〜〜〜〜〜

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

```swift
 pod init
```
ポッドファイルを作成。

```swift
    pod 'GoogleAPIClientForREST/Gmail', '~> 1.3.0'
    pod 'GTMAppAuth'
```
podファイルに上記を追加して

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

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

```swift
import AppAuth
import GoogleAPIClientForREST
import GTMAppAuth
```

```swift
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](https://console.cloud.google.com/apis)
上のリンクから認証情報のOAuth2.0クライアントIDに追加されているアプリの編集画面に行けばみれる！
``クライアントID``と``iOSのURLスキーム``あります。
__kClientID=クライアントID__
__kRedirectURL=iOSのURLスキーム+":/oauthredirect"__です
(:/oauthredirect"を忘れずに！　俺は忘れていた。)
###認証画面を表示させる
```swift
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")
                }
            }
        })
    }
```
軽く説明していきます。

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

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

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

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

```swift
 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](https://github.com/google/GTMAppAuth/blob/master/README.md)

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

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

// Remove from Keychain
GTMAppAuthFetcherAuthorization.removeFromKeychain(forName: kGTMAppAuthExampleAuthorizerKey)
```
一番上のコードでキーチェーンを利用して、ログイン状態を保存します。
いくつもログインしたい場合は、名前を変えて保存したらいいだけだから、すごい便利ですね。
したの記事でキーチェーンの理解をしておくといいかももも桃もお
[キーチェーンの記事](https://qiita.com/sachiko-kame/items/261d42c57207e4b7002a)

__ここまでで、認証完了！！__
#4.Gmail APIを叩く
やっと最後の章になります。長かった〜、、、、
##コード全体
```swift
@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()
    }

```
##コードの解説
説明ながなる〜〜〜〜〜〜〜〜疲れた〜〜〜〜〜〜〜〜

```swift
@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メソッドはボタンが押されたら呼ばれるメソッドです。
詳しく見ていきます。

```swift
let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)
```
GTLRGmailQuery_UsersMessagesList.queryはユーザーのメールボックス内のメッセージを一覧表示する命令です。useIdはGmailアドレスです。

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

```swift
 service.executeQuery( query,
                       delegate: self,
                       didFinish: #selector(callback) )
```
作業人が本人だったら作業開始します。
その時、callback関数の引数に結果が帰っていきます。
callbackの中身を具体的に見ていきます。

```swift
@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()
    }
```

```swift
if let error = error {
                print("メッセージリストの取得に失敗しました。")
                print(error)
                return
            }
```
引数のerrorに値が入っていたら、エラーを表示し、returnでcallback関数を抜けます。

```swift
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を生成します．

```swift
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については、通信に関するお話でよくわからないです。（ ｉ _ ｉ ）
また、勉強しておきます。
[Appleのドキュメント](https://developer.apple.com/documentation/foundation/nsurlrequest#1776617)に詳しく乗っています。

```swift
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まとめ](https://qiita.com/shiz/items/09523baf7d1cd37f6dee)
URLSession.sharedはURLSessionクラスにデフォルト値が設定されたインスタンスです。

```swift
if let err=err {
                print("失敗しました\(err)")
}
```
通信が完了して、エラーが帰ってきたらエラーを表示します

```swift
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は以下のようです。

```swift
struct res: Codable{
    var payload:Playload
    
    struct Playload:Codable {
        var headers:[Header]
    }
    struct Header:Codable {
        var name:String
        var value:String
    }
}
```
誰から、連絡きていたのか？を記事に載せようとしたけど、しょーもない人だったのでやめときます。。
#感想
初心者なのでわからないことばかりですが、少しでもお力になれれば幸いです。