PHPを使用してGmailを操作する

こんにちは、同僚。 この記事では、Gmail APIを使用した経験について説明します。 結局のところ、このトピックはインターネットであまり取り上げられておらず、ドキュメントは理想とはほど遠いものです。



最近、タスクがありました。ユーザーのGmailメールボックスでメッセージを検索するPHPアプリケーションを作成することです。 さらに、Gmailには「is:sent after:2012/08/10」のような記述が可能な優れた検索バーがあるため、単なる検索ではなく、パラメーターによる検索です。 はい、APIにはIMAPプロトコルX-GM-の拡張機能があります*



そのため、ユーザーを承認し、メッセージを検索するためのインターフェイスを実装する必要があります。 プロジェクトはZend Frameworkで書かれているため、これらの目的のためにZend Frameworkを使用しました。GoogleはAPIを操作するためにそれを使用することをお勧めします。



インターフェースの概要を説明します。



class Model_OAuth_Gmail { //   OAuth public function Connect( $callback ); //    Access Token (     ) public function getConnection($accessToken); //      const MODE_NONE = 0; const MODE_MESSAGES = 1; const MODE_THREAD = 2; //  :  (  getConnection ),     public function searchMessages($imapConnection, $params, $mode = 0); }
      
      







各メソッドはコメントで書きました。

注:はい、私はシングルトンとは何か、このクラスをこのように実装する必要があることを知っていますが、それはポイントではありません!



それでは、始めましょう:



つなぐ



  public function Connect( $callback ) { $this -> urls['callbackUrl'] = $callback; $session = new Zend_Session_Namespace('OAuth'); $OAuth_Consumer = new Zend_Oauth_Consumer(array_merge($this->config, $this->urls)); try { if (!isset($session -> accessToken)) { if (!isset($session -> requestToken)) { $session -> requestToken = $OAuth_Consumer -> getRequestToken(array('scope' => $this -> scopes), "GET"); $OAuth_Consumer -> redirect(); } else { $session -> accessToken = $OAuth_Consumer -> getAccessToken($_GET, $session -> requestToken); } } $accessToken = $session -> accessToken; $session -> unsetAll(); unset($session); return $accessToken; } catch( exception $e) { $session -> unsetAll(); throw new Zend_Exception("Error occurred. try to reload this page", 5); } }
      
      







それは非常に簡単です:セッションを開始し、Googleに投げてアクセス許可ボタンをクリックし、渡されたリクエストトークンを使用してアクセストークンを取得します



覚えておくべき主なことは、try-catchブロックを作成することです。 たとえば、ユーザーがクリックしてさらにクリックすると、セッションがクリアされるまでログインできなくなります(最初のステップでリクエストトークンが保存されます)!



まあ、私はほとんど設定を忘れていました:



  protected $config = array( 'requestScheme' => Zend_Oauth::REQUEST_SCHEME_HEADER, 'version' => '1.0', 'consumerKey' => 'anonymous', 'signatureMethod' => 'HMAC-SHA1', 'consumerSecret' => 'anonymous', ); protected $urls = array('callbackUrl' => "", 'requestTokenUrl' => 'https://www.google.com/accounts/OAuthGetRequestToken', 'userAuthorizationUrl' => 'https://www.google.com/accounts/OAuthAuthorizeToken', 'accessTokenUrl' => 'https://www.google.com/accounts/OAuthGetAccessToken' ); protected $scopes = 'https://mail.google.com/ https://www.googleapis.com/auth/userinfo#email';
      
      







getConnection



  public function getConnection($accessToken) { $config = new Zend_Oauth_Config(); $config -> setOptions($this::config); $config -> setToken(unserialize($user::accessToken)); $config -> setRequestMethod('GET'); $url = 'https://mail.google.com/mail/b/' . $user -> email . '/imap/'; $urlWithXoauth = $url . '?xoauth_requestor_id=' . urlencode($user -> email); $httpUtility = new Zend_Oauth_Http_Utility(); /** * Get an unsorted array of oauth params, * including the signature based off those params. */ $params = $httpUtility -> assembleParams($url, $config, array('xoauth_requestor_id' => $user -> email)); /** * Sort parameters based on their names, as required * by OAuth. */ ksort($params); /** * Construct a comma-deliminated,ordered,quoted list of * OAuth params as required by XOAUTH. * * Example: oauth_param1="foo",oauth_param2="bar" */ $first = true; $oauthParams = ''; foreach ($params as $key => $value) { // only include standard oauth params if (strpos($key, 'oauth_') === 0) { if (!$first) { $oauthParams .= ','; } $oauthParams .= $key . '="' . urlencode($value) . '"'; $first = false; } } /** * Generate SASL client request, using base64 encoded * OAuth params */ $initClientRequest = 'GET ' . $urlWithXoauth . ' ' . $oauthParams; $initClientRequestEncoded = base64_encode($initClientRequest); /** * Make the IMAP connection and send the auth request */ $imap = new Zend_Mail_Protocol_Imap('imap.gmail.com', '993', true); $authenticateParams = array('XOAUTH', $initClientRequestEncoded); $imap -> requestAndResponse('AUTHENTICATE', $authenticateParams); return $imap; }
      
      







この方法はGoogleの使用例であり、文書化されており、「そのまま」機能します。 さらに、それは非常に簡単です。



さて、 最も興味深いものに移りましょう:



searchMessages



最初に、アクションのアルゴリズム:

  1. パラメーターに基づいて検索バーを作成します
  2. 条件に一致するメッセージIDを見つける
  3. $モードに応じて変換します
  4. 利益! :)




アイテム1:


  $searchString = 'X-GM-RAW "'; foreach ($params as $key => $value) switch ($key) { // this is dates case "before" : case "after" : $searchString .= $key . ":" . date("Y/m/d", $value) . " "; break; // this is simple strings default : $searchString .= $key . ":" . $value . " "; break; } $searchString = trim($searchString) . '"';
      
      





パラメータを使用して配列を調べ、文字列に変換するだけです。 唯一の例外は日付であり、これを変換します。



アイテム2:


  $messages = $imapConnection -> search(array($searchString));
      
      





ちょうどいい? しかし、判明したように、このソリューションはまったく機能しません。 サーバーはエラーを出します、なぜなら EXAMINE“ INBOX”コマンドは実行しませんでした。 さて:



  if (isset($params['in'])){ $imapConnection->examine(strtoupper(($params['in']))); } else { $imapConnection->examine("INBOX"); } $messages = $imapConnection -> search(array($searchString));
      
      







このソリューションはすでに機能しており、ほぼ正常に機能しています。 しかし、発信(in:sent)を調べる必要があるとすぐに、間違った答えが返されます。 私はこの問題を掘り下げるのに多くの時間を費やし、答えが見つかりました。



Gmailのフォルダーの名前はSENT、INBOX、...ではなく、ロケール依存の名前(oO)であることが判明しました。 フォルダー名を変換する簡単な方法を作成する必要がありました。



  protected function getFolder($imap, $folder) { $response = $imap -> requestAndResponse('XLIST "" "*"'); $folders = array(); foreach ($response AS $item) { if ($item[0] != "XLIST") { continue; } $folders[strtoupper(str_replace('\\', '', end($item[1])))] = $item[3]; } return $folders[$folder]; }
      
      







フォルダのリストを見つけて、必要なものを見つけてください。 しかし、結局のところ、これだけではありません。 EXAMINEは問題を解決しませんが、検索する前にselectメソッドを呼び出してフォルダーを選択する必要があります。



  if (isset($params['in'])) $imapConnection -> select($this -> getFolder($imapConnection, strtoupper($params['in']))); $messages = $imapConnection -> search(array($searchString));
      
      





これで、見つかったメッセージのIDが得られました。小さなことは、メッセージのタイプに変換することです。



  switch ( $mode ) { case $this::MODE_NONE : return $messages; case $this::MODE_MESSAGES : // fetching (get content of messages) $messages = $imapConnection -> requestAndResponse("FETCH " . implode(',', $messages) . " (X-GM-THRID)"); return $messages; case $this::MODE_THREAD : $messages = $imapConnection -> requestAndResponse("FETCH " . implode(',', $messages) . " (X-GM-THRID)"); $storage = new Zend_Mail_Storage_Imap($imapConnection); $storage -> selectFolder( $this -> getFolder($imapConnection, strtoupper($params['in'])) ); $threads = array(); if ($messages) foreach ($messages AS $message) { if (isset($message[2][1])) { $thread_id = $message[2][1]; if (!isset($threads[$thread_id])) { $threads[$thread_id] = array('all' => $imapConnection -> requestAndResponse("SEARCH X-GM-THRID $thread_id"), 'my' => array()); unset($threads[$thread_id]['all'][0][0]); } $threads[$thread_id]['my'][] = $message[0]; } } $result = array(); foreach ($threads as $thread) if (!array_slice($thread['all'], array_search(max($thread['my']), $thread['all']) + 1)) $result[$storage -> getUniqueId(max($thread['my']))] = $storage -> getMessage(max($thread['my'])); return array_reverse($result); // for right order }
      
      







最初のケースでは、識別子の配列を返します。2番目のケースでは、メッセージ自体を受け取りますが、最も興味深い3番目のケースです。



ここでは、Zend_Mail_Storage_Imapを使用して、Zend_Mail_Messageの形式でメッセージを受信します。



Zend_Mail_Storage_Imapは選択したフォルダーについて何も知らないことを忘れないでください(メッセージの番号は異なります)。したがって、selectFolderメソッドを呼び出すことを忘れないでください。



変換プロセスは簡単です。メッセージスレッドを取得し、[すべてのメッセージ、私のメッセージ]という形式に変換します。 次に、スレッドの最後のメッセージを選択し、結果を作成します。



また、結果を裏返す必要があることを忘れないでください。 サーバーでの番号付けは古いものから新しいものに変わりますが、私たちはその逆に慣れています。



以上です! ご清聴ありがとうございました。 この記事がお役に立てば幸いです。



All Articles