LoginSignup
2

More than 5 years have passed since last update.

Googleハングアウトのメッセージを検索する(Gmailにアクセスできない環境用)

Last updated at Posted at 2015-09-30

諸事情でGmailにアクセスできない。
ドメイン制御がかかってる。
でもハングアウトは使ってるから検索したい。

もうしょうがないからAPIたたく!

流れ

  1. GmailAPIで検索 ⇒ ハイライトされた文字列とやりとり(thread)のIDを取得
  2. threadIdからその日のやりとりをさらに取得 ⇒ 日付と人の名前をパースして表示

1でテーブルに一覧化、行をクリックしたらそのやりとり詳細をmodalで表示するようにした。

参考

ソース

もうまるごと貼る。clientIdだけは各自で。

search_hangout.js

// https://console.developers.google.com/project/search-hangout-sample/apiui/credential/oauthclient/85744380901-k64k1r9hccgrlccmeob0pbqtuugkf6ll.apps.googleusercontent.com?quickstart=1
var clientId = "xxxxx";

var scopes = ["https://www.googleapis.com/auth/gmail.readonly"].join(",");
//https://mail.google.com/スコープはすべての権限を持ったスコープだけど、Threadやメッセージの削除を行いたい場合のみに利用するべき
//それ以外にいかが有る。
//https://www.googleapis.com/auth/gmail.modify
//https://www.googleapis.com/auth/gmail.readonly
//https://www.googleapis.com/auth/gmail.compose

function handleClientLoad() {
  window.setTimeout(checkAuth,1);
}

function checkAuth() {
  gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}

function handleAuthResult(authResult) {
  var $authorizeArea = $("#authorizeArea"),
  $buttonsArea = $("#buttonsArea");
  if (authResult && !authResult.error) {
    //エラーがなければ認可ボタン消して操作ボタン群オープンする
    $authorizeArea.hide();

    gapi.client.load("gmail","v1", function(){

    $buttonsArea
      .find("#searchButton")
        .on("click", search)
      .end()
      .show();
    });

  } else {
    $buttonsArea.hide();
    $authorizeArea
      .find("#authorize-button")
        .on("click", handleAuthClick)
      .end()
      .show();
  }
}

function handleAuthClick(event) {
  // Step 3: get authorization to use private data
  gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
  return false;
}

function search() {
  var searchText = $("#searchText").val();
  if (!searchText) return;

  // 初期化
  $("#result_table tbody *").remove();

  searchThreads(searchText, function(results) { makeResultTable(results) });
}

var searchThreads = function(searchText, func) {
  gapi.client.gmail.users.threads.list({
    userId:"me",
    q: searchText
  }).execute(function(response, component) {
    func(response.result.threads);
  });
}

var getThread = function(threadId, func) {
  if(threadId == "undefined") return;
  gapi.client.gmail.users.threads.get({
    id: threadId,
    userId:"me"
  }).execute(function(response , component) {
    func(response.result);
  });
}

var makeResultTable = function(results) {
  $.each(results, function(i, result) {
    appendSearchedSnippet(result.id, result.snippet);
  });

  prepareDetailTable();
}

// modalを閉じたときの初期化イベント
$('#detail_modal').on('hidden.bs.modal', function (e) {
  // 初期化
  $("#detail_table tbody *").remove();
})

var prepareDetailTable = function() {
  // 検索結果にmodalイベント
  // クリック時にやりとりを取得するイベント
  jQuery(function($) {
    $('#result_table tr').addClass('clickable')
      .click(function(e) {
        var threadId = $(this).find("td")[0].innerText;

        getThread(threadId, function(thread) { makeDetailTable(thread) });

        $('#detail_modal').modal('show');
    });
  });
}

var makeDetailTable = function(thread) {
  $.each(thread.messages, function(i, message) {
    appendDetail(
      formatDate(new Date(Number(message.internalDate))),
      message.payload.headers[0].value,
      message.snippet
    );
  });
}

var appendSearchedSnippet = function(threadId, snippet) {
  appendRow("#result_table tbody",
    "<td>" + threadId + "</td>" +
    "<td>" + snippet + "</td>"
  );
}

var appendDetail = function(date, from, snippet) {
  appendRow("#detail_table tbody",
    "<td>" + date + "</td>" +
    "<td>" + from + "</td>" +
    "<td>" + snippet + "</td>"
  );
}

var appendRow = function(table_selector, columns) {
  $(table_selector).append(
    "<tr>" +
      columns +
    "</tr>"
  );
}

var formatDate = function (date, format) {
  if (!format) format = 'YYYY-MM-DD hh:mm:ss';
  format = format.replace(/YYYY/g, date.getFullYear());
  format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
  format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2));
  format = format.replace(/hh/g, ('0' + date.getHours()).slice(-2));
  format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2));
  format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2));
  return format;
};

html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>はんぐあうとけんさく</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">
    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
    <!-- build:css(.tmp) styles/main.css -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <!-- endbuild -->
  </head>
  <body>
    <!--[if lt IE 10]>
      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
    <![endif]-->


    <div class="container">
      <div class="row" id="authorizeArea" style="display: none;">
        <div class="col-lg-12">
          <button id="authorize-button" class="btn btn-default">このアプリの許可</button>
        </div>
      </div>
      <div class="row" id="buttonsArea" style="display: none;">
        <div class="col-lg-3">
          <div class="form-inline">
            <div class="form-group">
              <input id="searchText" type="text" class="form-control">
              <button id="searchButton" type="button" class="btn btn-default">検索</button>
            </div>
          </div>
        </div>
      </div>
      <div class="table-responsive">
        <div id="result_area">
          <table id="result_table" class="table table-striped table-hover">
            <thead>
              <tr>
                <th>threadId</th>
                <th>snippet</th>
              </tr>
            </thead>
            <tbody>
            </tbody>
          </table>
        </div>
      </div>
      <div class="footer text-sm text-muted">
        <p><small>検索結果をクリックするとやりとりが取得できます</small></p>
        <p><small>うまくひっかからないと直近の100件くらいを取得するようです。あと何故か名前だと引っかからない場合があります。</small></p>
      </div>
    </div>

<div class="modal fade" id="detail_modal" tabindex="-1">
    <div class="modal-dialog modal-lg">

      <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal">
                <span aria-hidden="true">&times;</span>
            </button>
            <h4 class="modal-title" id="modal-label">やりとり(Ctrl+Fで検索してください)</h4>
        </div>
        <div class="modal-body">
          <div class="table-responsive">
            <table id="detail_table" class="table table-striped">
              <thead>
                <tr>
                  <th>date</th><th>from</th><th>snippet</th>
                </tr>
              </thead>
              <tbody>
              </tbody>
            </table>
          </div>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">閉じる</button>
        </div>
      </div>
    </div>
</div>

    <!-- build:js scripts/vendor.js -->
    <!-- bower:js -->
    <script src="assets/jquery-2.1.1.min.js"></script>
    <script src="assets/bootstrap.min.js"></script>
    <!-- endbower -->
    <!-- endbuild -->

    <!-- build:js({app,.tmp}) scripts/main.js -->
    <script src="assets/search_hangout.js"></script>
    <!-- endbuild -->
    <script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script> <!-- ① -->
  </body>
</html>

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