node.jsでSlackのリアクションを集計するツールを作ってみた

普段はWebのバックエンド開発をメインでやってます。

最近、WebサービスやDAppsの開発を始めた影響でJavaScriptを扱う機会が多くなり、ググった知識だけでは戦えないことに気づき勉強し直しました。

一通り学習し終えたのでnode.jsでSlackのリアクションを集計するツールを作ってみましたので紹介します。

ざっくりとした仕様

以下のような仕様で開発しました。

  • プログラムは定期的(1回/日)に実行
  • 前日にリアクションされた絵文字を集計 ※publicチャンネルのみ
  • 集計結果を降順でソート
  • 結果を特定のチャンネルに送信

そもそもリアクションって何?って方はこちらへ。

絵文字でリアクションする | Slack

必要なAPIを選定する

フローを簡単にまとめると

  1. 対象チャンネルを取得
  2. 各チャンネル内の会話(リアクション)を取得
  3. リアクションを集計→送信

となります。

1と2はAPIで取得する情報になります。

良さげなやつがありました。

channels.list method | Slack

channels.history method | Slack

channels.listで対象チャンネルを取得、取得したchannelコードをchannels.historyに引き渡しリアクションを取得します。

channels.historyを実行するとき取得期間を絞るため、oldestに前日0時0分、latestに当日0時0分を設定します。

※厳密にやると0時0分が重複するからダメかもしれない。

Slackにアプリを登録する

事前準備としてSlackにアプリを登録し、Botユーザを作成します。

詳しくはこちらへ。

ワークスペースで利用するボットの作成 | Slack

channels.historyの実行にchannels:history readの権限が必要なので付与しておきましょう。

合わせてOAuth Access TokenとBot User OAuth Tokenが必要となるのでメモっておきます。

f:id:maroemon58:20180806181325p:plain

実装

slack-reaction | github.com

必要なパラメータ(OAuth Access TokenとBot User OAuth Token、送信先チャンネル)は.envファイルに記載しています。


// load .env
require('dotenv').config();
const SLACK_TOKEN = process.env.SLACK_TOKEN;
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
const SEND_CHANNEL = process.env.SEND_CHANNEL;

const util = require('util');
const slack = require('slack');
const moment = require('moment-timezone');

const CronJob = require('cron').CronJob;

function getChannels(oldest, latest) {
  return new Promise(function(onFulfilled, onRejected) {
    slack.channels.list({ token: SLACK_TOKEN }).then(response => {
      onFulfilled({
        channels: response.channels.map(channel => channel.id),
        oldest: oldest,
        latest: latest
      });
    });
  });
}

function getReactions(responses) {
  return new Promise(function(onFulfilled, onRejected) {
    reactions = [];

    const channels = responses.channels;
    const oldest = responses.oldest;
    const latest = responses.latest;

    let i = 0;
    channels.map(channel => {
      slack.channels
        .history({
          token: SLACK_TOKEN,
          channel: channel,
          oldest: oldest,
          latest: latest
        })
        .then(response => {
          response.messages.map(message => {
            if ('reactions' in message) {
              reactions = reactions.concat(message.reactions);
            }
          });
          if (++i == channels.length) {
            onFulfilled(reactions);
          }
        })
        .catch(error => {
          console.log('error!!');
        });
    });
  });
}

sortReaction = reactions => {
  // counter
  totalReaction = [];

  reactions.map(reaction => {
    name = ':' + reaction.name + ':';
    if (name in totalReaction === false) {
      totalReaction[name] = 0;
    }
    totalReaction[name] += reaction.count;
  });

  // convert for sort
  sortReaction = [];
  for (name in totalReaction) {
    sortReaction.push({
      name: name,
      count: totalReaction[name]
    });
  }

  sortReaction = sortReaction.sort((a, b) => {
    if (a.count > b.count) return -1;
    else if (a.count < b.count) return 1;
    else return 0;
  });

  // create send message
  msg = '';
  for (reaction of sortReaction) {
    msg += reaction.name + ' : ' + reaction.count + '\n';
  }

  return msg;
};

postMessage = () => {
  // oldest ※前日0時0分
  oldest = moment()
    .subtract(1, 'days')
    .startOf('day');

  latest = moment().startOf('day');

  getChannels(oldest.unix(), latest.unix())
    .then(getReactions)
    .then(reactions => {
      if (reactions.length === 0) {
        msg =
          'Good Morning!! ' +
          oldest.format('M/D(ddd)') +
          'のリアクションは・・・なしです。';
      } else {
        msg =
          'Good Morning!! ' +
          oldest.format('M/D(ddd)') +
          'のリアクション集計しました。\n\n';
        msg += sortReaction(reactions);
      }

      console.log(msg);

      slack.chat.postMessage({
        token: SLACK_BOT_TOKEN,
        channel: SEND_CHANNEL,
        text: msg
      });
    });
};
const job = new CronJob({
  /*
  Seconds: 0-59
  Minutes: 0-59
  Hours: 0-23
  Day of Month: 1-31
  Months: 0-11
  Day of Week: 0-6
  */
  cronTime: '0 0 9 * * *', // 毎日午前9時に送信
  onTick: postMessage,
  start: false,
  timeZone: 'Asia/Tokyo'
});
job.start();

正しく動けばこんな感じでBotユーザから送信されます。

sample

GitHubにインストール手順をまとめているので、使ってみたい方はどうぞ。

まとめ

実は絶賛開発中の社内通貨プロジェクトで利用しようかなって考えてます。

NEMモザイクを利用した社内通貨を提案するまで

当時はSlackを利用してなかったので、別のツールを利用してイイねのやり取りを行う予定でした。

社内のチャットツールをSlackに切り替えリアクションのやり取りが予想以上に活発なので、データを収集して活用する術を頭の中で妄想してます。

たぶん特定のリアクションを貰う(贈る)と通貨がもらえるような感じになりそうです。

ではまた。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

ABOUTこの記事をかいた人

Yusuke Ito

■25歳時に異業種からWeb系企業に転職、現在はフリーランスとして活動 ■バックエンド(PHP、Ruby、Python)がメイン、フロントエンドも少々 ※ECサイトの開発が得意 ■筋トレ、ゴルフ、読書、ポケモンが趣味 ■エンジニアとしてのキャリア、アプリ開発、書評をメインに発信しています。