Slackの過去ログをGmailに定期的に飛ばしてアーカイブ（Google Apps Script）



あのやりとりしたのGmailだっけ、Slackだっけ、こっちで検索し、あっちで検索しというのも面倒！
GmailをSlackに転送する方法は普通に紹介されていますけど、逆はなかなか見つからない・・・
無料枠で過去ログの制限もあるし、SlackログをGmailにアーカイブさせて使うのも悪くないのでは？
Google Apps Scriptを使うとサーバいらずで定期実行できるし、という事でさくっと書いてみました。

## SlackにてWeb API用トークン生成

[SLACK API Authentication](https://api.slack.com/web#authentication)にアクセスし「Generate test tokens」を実行します。

![スクリーンショット 2016-10-05 12.57.00.png](https://qiita-image-store.s3.amazonaws.com/0/15518/4232ab16-64b9-fe35-084e-01bdfa8298dd.png "スクリーンショット 2016-10-05 12.57.00.png")

「Create token」するとパスワードなど聞かれるのでconfirmするとxoxp-から始まるtokenが生成されるのでメモります。
もちろんこのtokenが漏洩すると大変な事になるので注意してくださいね。
![スクリーンショット 2016-10-05 12.59.02.png](https://qiita-image-store.s3.amazonaws.com/0/15518/28aa7cd1-9402-dd2e-a2a9-c6b03c08f30b.png "スクリーンショット 2016-10-05 12.59.02.png")


## スクリプトの作成

動作仕様
* チャンネル毎にログをメール送信
* 自分の参加しているチャンネルのみ対象
* 初回は取得できる全てのログを送信し、次回以降は最終取得日時以降のログのみ送信
* メールで送信する最大文字数を超えた場合はカット


スクリプトの保存場所は[Googla Drive](https://www.google.com/intl/ja_jp/drive/)。
Apps Scriptを初めて使う場合は「新規」>「その他」>「アプリを追加」から「Google Apps Script」を追加してください。

![スクリーンショット 2016-10-05 12.30.15.png](https://qiita-image-store.s3.amazonaws.com/0/15518/b00c4387-1538-ab71-94c6-895b71cfea0b.png "スクリーンショット 2016-10-05 12.30.15.png")
※ 社用のAppsで作業していますがお気になさらず

![スクリーンショット 2016-10-05 12.31.38.png](https://qiita-image-store.s3.amazonaws.com/0/15518/1dacb4ea-fb57-29a0-e893-dd3b9eaaa27e.png "スクリーンショット 2016-10-05 12.31.38.png")

新規Google Apps Scriptを作成します。
![スクリーンショット 2016-10-05 12.32.19.png](https://qiita-image-store.s3.amazonaws.com/0/15518/1d3c1015-2fcc-c8bb-b460-c9f7dc824f92.png "スクリーンショット 2016-10-05 12.32.19.png")

プロジェクト名はー、「Slack2Gmail」にしてみます。
![スクリーンショット 2016-10-05 13.14.01.png](https://qiita-image-store.s3.amazonaws.com/0/15518/b0aaad5e-4e12-1f07-d39d-463b29ba20f9.png "スクリーンショット 2016-10-05 13.14.01.png")

コードに下記スクリプトを貼り付けます。下記書き換えてください。
[Your Slack token] => 先ほど取得したSlackのAPIトークンに
[Your Mail Address] => 送信先メールアドレスに

```
// Configuration: Obtain Slack web API token at https://api.slack.com/web
var API_TOKEN = '[Your Slack token]'
var MAIL_TO = '[Your Mail Address]';
// Slack offers 10,000 history logs for free plan teams
var MAX_HISTORY_PAGINATION = 10;
var HISTORY_COUNT_PER_PAGE = 1000;
var MAX_MAIL_LENGTH = 10000000;
var prop = {};
function StoreLogsDelta() {
	var logger = new SlackChannelHistoryLogger();
	logger.run();
};

var SlackChannelHistoryLogger = (function () {
	function SlackChannelHistoryLogger() {
		this.memberNames = {};
	}

	SlackChannelHistoryLogger.prototype.requestSlackAPI = function (path, params) {
		if (params === void 0) {
			params = {};
		}
		var url = "https://slack.com/api/" + path + "?";
		var qparams = [("token=" + encodeURIComponent(API_TOKEN))];
		for (var k in params) {
			qparams.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
		}
		url += qparams.join('&');
		Logger.log("==> GET " + url);
		var resp = UrlFetchApp.fetch(url);
		var data = JSON.parse(resp.getContentText());
		if (data.error) {
			throw "GET " + path + ": " + data.error;
		}
		return data;
	};
	SlackChannelHistoryLogger.prototype.run = function () {
		var _this = this;
		var p = PropertiesService.getScriptProperties().getProperties();
		if (p != null) {
			_this.prop = p;
		} else {
			_this.prop = {};
		}
		var usersResp = this.requestSlackAPI('users.list');
		usersResp.members.forEach(function (member) {
			_this.memberNames[member.id] = member.name;
		});
		var teamInfoResp = this.requestSlackAPI('team.info');
		this.teamName = teamInfoResp.team.name;
		var channelsResp = this.requestSlackAPI('channels.list');

		var title = "";
		var body = "";
		for (var _i = 0, _a = channelsResp.channels; _i < _a.length; _i++) {
			var ch = _a[_i];
			if (ch.is_member) {
				body = this.importChannelHistoryDelta(ch);
				if (body != "") {
                  if (body.length > MAX_MAIL_LENGTH) {
                    body = body.substr(0, MAX_MAIL_LENGTH);
                    body += "\n--- string length over " + MAX_MAIL_LENGTH + " ---\n";
                  }
					// send mail
					title = "[Slack Log][channel:" + ch.name + "] " + _this.formatDate(new Date());
					MailApp.sendEmail({
						to: MAIL_TO,
						subject: title,
						body: body
					});
				}
			}
		}
		PropertiesService.getScriptProperties().setProperties(_this.prop);
	};
	SlackChannelHistoryLogger.prototype.importChannelHistoryDelta = function (ch) {
		var _this = this;
		Logger.log("importChannelHistoryDelta " + ch.name + " (" + ch.id + ")");
		var oldestKey = ch.name + '-oldest';
		var oldest = 1;
		if (_this.prop[oldestKey] != null) {
			oldest = _this.prop[oldestKey];
		}
		var options = {};
		if (oldest != null) {
			options['oldest'] = oldest;
		}

		var todayString = _this.formatDate(new Date());
		var messages = this.loadMessagesBulk(ch, options);
		var dateStringToMessages = {};
		messages.forEach(function (msg) {
			var date = new Date(+msg.ts * 1000);
			var dateString = _this.formatDate(date);
			if (!dateStringToMessages[dateString]) {
				dateStringToMessages[dateString] = [];
			}
			dateStringToMessages[dateString].push(msg);
			_this.prop[oldestKey] = msg.ts;
		});

		var body = "";
		if (dateStringToMessages != null && dateStringToMessages.length != 0) {
			for (var dateString in dateStringToMessages) {
				body += "=================== date:[" + dateString + "] ===================\n\n";
				dateStringToMessages[dateString].forEach(function (msg) {
					var date = new Date(+msg.ts * 1000);
					var user = msg.user;
					if (_this.memberNames[msg.user] != null) {
						user = _this.memberNames[msg.user];
					}
					body += '[' + Utilities.formatDate(date, Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm:ss') + '] ' +
						'from:' + user + "\n" +
						_this.unescapeMessageText(msg.text) + "\n";
				});
			}
		}
		return body;
	};
	SlackChannelHistoryLogger.prototype.formatDate = function (dt) {
		return Utilities.formatDate(dt, Session.getScriptTimeZone(), 'yyyy-MM-dd');
	};
	SlackChannelHistoryLogger.prototype.loadMessagesBulk = function (ch, options) {
		var _this = this;
		if (options === void 0) {
			options = {};
		}
		var messages = [];
		options['count'] = HISTORY_COUNT_PER_PAGE;
		options['channel'] = ch.id;
		var loadSince = function (oldest) {
			if (oldest) {
				options['oldest'] = oldest;
			}
			// order: recent-to-older
			var resp = _this.requestSlackAPI('channels.history', options);
			messages = resp.messages.concat(messages);
			return resp;
		};
		var resp = loadSince();
		var page = 1;
		while (resp.has_more && page <= MAX_HISTORY_PAGINATION) {
			resp = loadSince(resp.messages[0].ts);
			page++;
		}
		// oldest-to-recent
		return messages.reverse();
	};
	SlackChannelHistoryLogger.prototype.unescapeMessageText = function (text) {
		var _this = this;
		return (text || '')
			.replace(/&lt;/g, '<')
			.replace(/&gt;/g, '>')
			.replace(/&quot;/g, '"')
			.replace(/&amp;/g, '&')
			.replace(/<@(.+?)>/g, function ($0, userID) {
				var name = _this.memberNames[userID];
				return name ? "@" + name : $0;
			});
	};
	return SlackChannelHistoryLogger;
})();

```

[参考サイト](http://motemen.hatenablog.com/entry/2015/11/gas-slack-log-spreadsheet)のスプレットシートにログ保存するスクリプトをメール送信する仕組みに書き換えました。
スプレットシートの最終行から日時を取得し以降のログだけ取得する機能がありましたが、この最終日時をスクリプトプロパティに持たせるように変更しています。


ここで一度実行ボタンを押して権限の許可をしておきます。

![スクリーンショット 2016-10-05 13.47.57.png](https://qiita-image-store.s3.amazonaws.com/0/15518/bfda81ae-56a4-586e-eef6-d8563441e13f.png "スクリーンショット 2016-10-05 13.47.57.png")

正常に実行できればこの時点でメールも届くはずです。「表示」>「ログ」にログで出力内容も確認できます。
またスクリプトを実行すると最終取得日時の情報が「ファイル」>「プロジェクトのプロパティ」>「スクリプトのプロパティ」にチャンネル毎の最終取得日時が保存されているのでまた最初から取得したい場合はこのプロパティを削除してください。
メール送信文字数の最大値はGmailがMAXで25MBまで送れるらしいということで2バイトコードで10000000で20MB+バッファ5MBに設定してみました。



![スクリーンショット 2016-10-05 17.59.26.png](https://qiita-image-store.s3.amazonaws.com/0/15518/5f94bcad-6a73-0bec-bcb9-7c78eb249a5b.png "スクリーンショット 2016-10-05 17.59.26.png")


## 定期実行

ツールバーの時計の様なアイコンから実行スケジュールを設定します。
お好みのタイミングに指定してみると良いでしょう。

![スクリーンショット 2016-10-05 17.16.27.png](https://qiita-image-store.s3.amazonaws.com/0/15518/8a662aaa-7f19-d2ed-76f0-77aca54893ad.png "スクリーンショット 2016-10-05 17.16.27.png")

![スクリーンショット 2016-10-05 18.28.43.png](https://qiita-image-store.s3.amazonaws.com/0/15518/93a206fd-a971-d87e-dc71-16831a1f5a0e.png "スクリーンショット 2016-10-05 18.28.43.png")


以上で設定完了です！
Gmail側でいい感じにアーカイブのフィルタをかけてあげれば受信トレイを汚さずにSlackログが検索にかかるGmailの完成です。
![スクリーンショット 2016-10-06 12.35.25.png](https://qiita-image-store.s3.amazonaws.com/0/15518/57eafac3-84fa-7ff1-88dc-a36223ff127d.png "スクリーンショット 2016-10-06 12.35.25.png")
![スクリーンショット 2016-10-06 12.35.56.png](https://qiita-image-store.s3.amazonaws.com/0/15518/9220ed94-c09a-c97f-9db6-710fbcd8c55e.png "スクリーンショット 2016-10-06 12.35.56.png")




> 参考サイト
> [Slack のログを自動で Google Spreadsheet に保存する](http://motemen.hatenablog.com/entry/2015/11/gas-slack-log-spreadsheet)
> [Google Apps Scriptを使って定期実行するcronを作る](http://www.sakana.tech/entry/make-cron-use-googleappsscript)