10
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Slackの過去ログをメール転送する

Last updated at Posted at 2017-06-26

Slack は無料プランだと10000件までのメッセージしか保存してくれませんので、それ以前のメッセージは消えてなくなってしまいます。有償プランにすればもっと保存できますが、SlackのUIで古いメッセージを探すのはなかなか大変です。過去ログをメールで転送しておけば古いメッセージもずっと残しておけますし、検索もメールソフトで強力に行えます。ということで、過去ログをメール転送するPerlスクリプトを作ってみました。

(2020/2/7追記)

無料プランでの Slack の過去メッセージは消えてしまうわけではなく不可視になるだけでした。有料プランに切り替えることで取得することができます。

(2020/2/7追記終わり)

Slackアプリの作成

Slack APIの記事を検索しているとレガシートークンを用いたものがよくみつかります。レガシートークンは全権限が付与されるので楽なんですが、現在は非推奨だそうです。ですのでSlackアプリの作成という方法を採ります。

Building Slack appsにアクセスし、「Create a Slack App」というボタンをクリックします。

image.png

「Create an App」というダイアログが表示されますので、「App Name」に作成するアプリの名称を入力し、「Development Slack Team」で対象となるSlack Teamを選択し、「Create App」をクリックします。

image.png

「Building Apps for Slack」画面で「Permissions」をクリックします。

image.png

「OAuth & Permissions」画面の少し下にある「Permission Scopes」で次のpermissionsを追加します。

  • channels:history
  • users.profiles:read

追加したら「Save Changes」をクリックします。

image.png

「Success!」と表示されたら、「OAuth Tokens & Redirect URLs」に戻って「Install App to Team」をクリックします。

image.png

しばらく待って準備が完了したら「Install」ボタンをクリックします。

image.png

インストールに成功すると、アクセストークンが表示されます。このトークンを使ってSlack APIにアクセスできます。

image.png

スクリプト

先ほど取得したトークンを下記のソースの 'token' => '<token>' のところに貼り付ければOKです。

スクリプトの動作としては、引数無しで起動すると昨日の一日分の全てのチャネルの全てのメッセージを取得してメール送信します。引数で数字8ケタで日付(YYYYMMDD)を指定すると、その日付の一日分のメッセージをメール送信します。あとは詳しくはソースをご覧ください。

#!/usr/bin/perl

use strict;
use warnings;
use Encode;
use utf8;

use LWP::Simple;
use JSON::XS;
use Time::Local;
use Net::SMTP;

our $config = {
	'mail' => {
		'smtp' => 'smtp.gmail.com',
		'port' => 587,
		'user' => '<account>',
		'pass' => '<password>',
		'from' => '<account>'
	},
	'slack' => {
		'token' => '<token>'
	}
};

my $to = 'saoyagi2@gmail.com';

{
	my $targetdate;
	if( @ARGV > 0 && $ARGV[0] =~ /^\d{8}$/ ) {
		$targetdate = $ARGV[0];
	}
	else {
		my @t = localtime( time() - 86400 );
		$targetdate = sprintf( "%04d%02d%02d", $t[5] + 1900, $t[4] + 1, $t[3] );
	}
	my $oldest = timelocal( 0, 0, 0, substr( $targetdate, 6, 2 ), substr( $targetdate, 4, 2 ) - 1, substr( $targetdate, 0, 4 ) - 1900 );
	my $latest = $oldest + 86400;

	my $users_list = slack_api( 'users.list', $config->{'slack'}->{'token'} ) or die;
	my %members;
	for my $user ( @{$users_list->{'members'}} ) {
		$members{$user->{'id'}} = $user->{'name'};
	}

	my $channels_list = slack_api( 'channels.list', $config->{'slack'}->{'token'} ) or die;
	my %channels;
	for my $channel ( @{$channels_list->{'channels'}} ) {
		$channels{$channel->{'id'}} = $channel->{'name'};
	}

	my $body = '';
	foreach my $channel_id ( keys( %channels ) ) {
		$body .= sprintf( "### %s ###\n\n", $channels{$channel_id} );

		my @messages;
		while(1) {
			my $channel_history = slack_api( 'channels.history', $config->{'slack'}->{'token'}, {
				'channel'=>$channel_id,
				'oldest'=>$oldest,
				'latest'=>$latest,
				'inclusive'=>0
			} ) or die;
			push( @messages, @{$channel_history->{'messages'}} );
			if( !@{$channel_history->{'messages'}} || !$channel_history->{'has_more'} ) {
				last;
			}
			$latest = $channel_history->{'messages'}->[-1]->{'ts'};
		}

		for my $message ( reverse @messages ) {
			next if( $message->{'type'} ne 'message' );
			my @t = localtime( $message->{'ts'} );
			$body .= sprintf( "[%s]%02d:%02d:%02d\n", $members{$message->{'user'}}, $t[2], $t[1], $t[0] );
			$body .= $message->{'text'} . "\n\n";
		}

		$body .= "\n";
	}

	my $subject = sprintf( "[Slack]%d/%d/%d", 
		substr( $targetdate, 0, 4 ),
		substr( $targetdate, 4, 2 ),
		substr( $targetdate, 6, 2 ) );

	my $data = <<"DATA";
To: $to
Subject: $subject
Content-Type: text/plain; charset=\"UTF-8\"

$body
DATA

	my $mail = Net::SMTP->new( $config->{'mail'}->{'smtp'},
		Hello=>$config->{'mail'}->{'smtp'},
		Port=>$config->{'mail'}->{'port'}
	);
	$mail->starttls();
	$mail->auth( $config->{'mail'}->{'user'}, $config->{'mail'}->{'pass'} );
	$mail->mail( $config->{'mail'}->{'from'} );
	for my $_to ( split( ',', $to ) ) {
		$mail->to( $_to );
	}
	$mail->data();
	$mail->datasend( encode( 'UTF-8', $data ) );
	$mail->dataend();
	$mail->quit;
}

sub slack_api {
	my( $path, $token, $params_ref ) = @_;

	my $url = "https://slack.com/api/${path}?token=" . urlencode( $token );
	for my $key ( keys( %$params_ref ) ) {
		$url .= "&" . urlencode( $key ) . "=" . urlencode( $params_ref->{$key} );
	}
	my $retry = 3;
	while( $retry >= 0 ) {
		my $result = get( $url );
		if( defined( $result ) ) {
			return decode_json( $result );
		}
		$retry--;
	}
	return undef;
}

sub urlencode {
	my $str = shift;

	$str =~ s/([^ 0-9a-zA-Z])/"%".uc(unpack("H2",$1))/eg;
	$str =~ s/ /+/g;

	return $str;
}

(2017/8/17追記)

書き忘れてましたが、LWP::Simpleでhttps通信を行うには以下のモジュールが必要です。

  • Net::SSLeay
  • Crypt::SSLeay
  • IO-Socket::SSL

全部必須かどうかはちょっと不確かです。適当に見つけた記事の記述に従って入れたら動いたので、とりあえずそのままにしてるというアバウトな状態でして(汗

あと、意外とタイムアウトでAPI呼び出しに失敗しているようなので、リトライを入れてみました。

10
16
0

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
10
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?