諸事情でGmailにアクセスできない。
ドメイン制御がかかってる。
でもハングアウトは使ってるから検索したい。
もうしょうがないからAPIたたく!
流れ
- GmailAPIで検索 ⇒ ハイライトされた文字列とやりとり(thread)のIDを取得
- threadIdからその日のやりとりをさらに取得 ⇒ 日付と人の名前をパースして表示
1でテーブルに一覧化、行をクリックしたらそのやりとり詳細をmodalで表示するようにした。
参考
-
Google I/O 2014の裏でひっそり公開されたGmail APIを触ってみる
- GmailAPIの基盤はまるっとそのまま頂きました。大変参考になりましたありがとうございます。
- Gmail API
ソース
もうまるごと貼る。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">×</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>