人生なんて暇つぶし

Webエンジニアの雑記ブログ。技術の話題はDApps開発、NEMアプリ開発がメイン。

移転しました。

約3秒後に自動的にリダイレクトします。

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

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

最近プライベートでWebサービスやDAppsの開発を始め、JavaScriptを扱う機会が多くググった知識だけでは戦えなくなって勉強し直しました。

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

ざっくりとした仕様

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

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

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

get.slack.help

必要な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ユーザを作成します。

詳しくはこちらへ。

get.slack.help

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

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

f:id:maroemon58:20180806181325p:plain

実装

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にインストール手順をまとめているので、使ってみたい方はどうぞ。

まとめ

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

www.maroemon.com

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

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

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

ではまた。

twitter.com