まいける's Tech Blog

LAMP関係のメモなどを不定期に掲載します

Gmail から特定の条件を満たすメールを抽出して RSS 化する

 ちょっとした必要に迫られて作ってみました。Gmail にはもともと、特定のラベルが付いたメールを ATOM で出力する機能が付いているのですが(このあたりの記事を参照)、HTTP 認証を使っているので、Feedly では利用することができません。ということで、Gmail API を利用して条件を満たすメールを抽出したうえで、RSS 化してみました。

 用意するものは

の2つ。Composer などを利用してインストールしておいてください。

 また、Gmail API を利用するための準備については、Google Developers のこちらの記事をご覧ください。RSS 化するスクリプトは、cron で定期的に実行することを念頭に置いているので、アプリケーションの種類は、“インストールされているアプリケーション”を選びます。

 以下、コードです。

<?php
require 'vendor/autoload.php';  // composer の autoload.phpの場所を指定

define('APPLICATION_NAME', 'Gmail To RSS');
define('CREDENTIALS_PATH', '~/.credentials/gmail-to-rss.json');
define('CLIENT_SECRET_PATH', 'client_secret.json'); // 準備で作成したクライアントIDに対応したJSONファイルの場所を指定
define('SCOPES', implode(' ', array(
  Google_Service_Gmail::GMAIL_READONLY) // 今回のスクリプトではメールに編集を加える必要がないので、与える権限は読み込みのみに
));

/**
 * Returns an authorized API client.
 * @return Google_Client the authorized client object
 */
function getClient() {
  $client = new Google_Client();
  $client->setApplicationName(APPLICATION_NAME);
  $client->setScopes(SCOPES);
  $client->setAuthConfigFile(CLIENT_SECRET_PATH);
  $client->setAccessType('offline');

  // Load previously authorized credentials from a file.
  $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);
  if (file_exists($credentialsPath)) {
    $accessToken = file_get_contents($credentialsPath);
  } else {
    // Request authorization from the user.
    $authUrl = $client->createAuthUrl();
    printf("Open the following link in your browser:\n%s\n", $authUrl);
    print 'Enter verification code: ';
    $authCode = trim(fgets(STDIN));

    // Exchange authorization code for an access token.
    $accessToken = $client->authenticate($authCode);

    // Store the credentials to disk.
    if(!file_exists(dirname($credentialsPath))) {
      mkdir(dirname($credentialsPath), 0700, true);
    }
    file_put_contents($credentialsPath, $accessToken);
    printf("Credentials saved to %s\n", $credentialsPath);
  }
  $client->setAccessToken($accessToken);

  // Refresh the token if it's expired.
  if ($client->isAccessTokenExpired()) {
    $client->refreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, $client->getAccessToken());
  }
  return $client;
}

/**
 * Expands the home directory alias '~' to the full path.
 * @param string $path the path to expand.
 * @return string the expanded path.
 */
function expandHomeDirectory($path) {
  $homeDirectory = getenv('HOME');
  if (empty($homeDirectory)) {
    $homeDirectory = getenv("HOMEDRIVE") . getenv("HOMEPATH");
  }
  return str_replace('~', realpath($homeDirectory), $path);
}

// Get the API client and construct the service object.
$client = getClient();

$service = new Google_Service_Gmail($client);

$user = 'me';

$optParams = array();
$optParams['maxResults'] = 50;
$optParams['q'] = 'label:hogehoge is:unread'; // hogehogeというラベルが付いた未読のメールを検索

try {
    $messages = $service->users_messages->listUsersMessages($user, $optParams); // 条件に合致するメールの一覧を取得

    $list = $messages->getMessages();

    $i = 0;

    foreach($list as $line) {
        $messageId = $line->getId();

        $optParamsGet = array();
        $optParamsGet['format'] = 'full'; // Display message in payload
        try {
            $message = $service->users_messages->get($user, $messageId, $optParamsGet); // 取得したメールIDから個々のメールデータを取得
            
            $messagePayload = $message->getPayload();

            $headers = $message->getPayload()->getHeaders();

            foreach($headers as $header) {
                $header_ary[$header->getName()] = $header->getValue(); // ヘッダ情報を後で使いやすいように配列に代入
            }

            $items[$i]['title'] = $header_ary['Subject'];
            $items[$i]['date'] = strtotime($header_ary['Date']);
            $items[$i]['message_id'] = $messageId;

            $body = $message->getPayload()->getBody(); // メール本文を取得

            $rawData = $body->data;
            $sanitizedData = strtr($rawData, '-_', '+/');
            $decodedMessage = base64_decode($sanitizedData); // Base64でエンコードされているのでデコード

            $items[$i]['description'] = $decodedMessage;

            $i ++;

        } catch (apiServiceException $e) {
           // Error from the API.
            print 'There was an API error : ' . $e->getCode() . ' : ' . $e->getMessage();
        } catch (Exception $e) {
            print 'There was a general error : ' . $e->getMessage();
        }
    }


} catch (apiServiceException $e) {
    // Error from the API.
    print 'There was an API error : ' . $e->getCode() . ' : ' . $e->getMessage();
} catch (Exception $e) {
    print 'There was a general error : ' . $e->getMessage();
}

// ここから RSS 化

date_default_timezone_set("Asia/Tokyo");

use \FeedWriter\ATOM;
$feed = new ATOM;

//チャンネル情報の登録
$feed->setTitle("hogehoge");          //チャンネル名
$feed->setLink("http://www.example.com/");     //URLアドレス
$feed->setDate(new DateTime());         //日付 (変更不要)

foreach($items as $tmp_item) {
    // メール本文中のURLを自動リンク化
    $description = mbereg_replace("(https?|ftp)(://[[:alnum:]\+\$\;\?\.%,!#~*/:@&=_-]+)", "<a href=\"\\1\\2\" target=\"_blank\">\\1\\2</a>" , $tmp_item['description']);
    // 改行を<br />に変換
    $description = nl2br($description);
        
    $item = $feed->createNewItem();
    $item->setTitle($tmp_item['title']);
    $item->setLink('http://www.example.com/archive.php?message_id=' . $tmp_item['message_id']); // この部分については次の記事で
    $item->setDate($tmp_item['date']);
    $item->setDescription($description);
    $feed->addItem($item);
}


file_put_contents('/path/to/hogehoge.rdf', $feed->generateFeed()); // ファイルに書き出す
?>

 このファイルに gmail2rss.php のような適当な名前を付けてサーバにアップロードします。CREDENTIALS_PATH や CLIENT_SECRET_PATH で指定している場所は、外部から見えない場所に設定してください。
 そのうえで、ターミナルから

php gmail2rss.php

を実行すると、初回は

Open the following link in your browser: https://〜

のようなメッセージが表示されますので、指定された URL にブラウザからアクセスしてください。このスクリプトに権限を与えてよいかのダイアログが表示されるので、許可すると、コードが表示されます。それをコピーしてターミナルに戻り

Enter verification code: 

に続けてペーストすると、スクリプトが実行され、指定された場所に RSS ファイルが生成されます。

 あとはこれを RSS リーダーで読めばよいのですが、このままだと、RSS リーダーで既読にしても、Gmail 側は未読のままです。そこで、記事の URL として指定された URL にアクセスすると Gmail 側も既読にしてアーカイブするようにしてみたいと思います。