4
1

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.

#はじめに
この記事はトラストバンクアドベントカレンダー13日目の記事です。

こんにちは!トラストバンクのテックリード 海塩(うしお)(@youhei_seasalt)と申します。
会社として初の試みであるアドベントカレンダーですが、毎日同僚の記事が提供されるというのはとても楽しいですね。

#なにをしたか
最近SlackのAPIを触ることがあったため、リアクションを集計して遊びました。
一番多く使われたリアクションや、一番多くリアクションを受けた人を集計できます。
Slack上でおもむろに↓こんな発言をしようとしています。
reaction1.png
GASのほうが親和性が高く、ネット上のサンプルも多いですがPHPerなのであえてPHPでやってみます。

#APIを探す
公式のAPIページを漁ります。
このAPIでメッセージの履歴が取得できそうです。
https://api.slack.com/methods/conversations.history

#APIトークンはどこで?
まずはSlack APIを使えるようにしなければいけません。
いつものように「slack api 使い方」でググります。
いくつかページを見て、Slackアプリを作ればいけそうとわかりました。
早速作ります。
権限はこのくらい付けておけばいいでしょう。
permission.png

#APIを呼んでみる
マニュアルみながらコードを組んでみます。

$client = new \GuzzleHttp\Client();
$token = 'xoxp-xxxxx-xxxxx-xxxxx......';
$response = $client->request(
    method: 'GET',
    uri: 'https://slack.com/api/conversations.history',
    options: [
        'headers' => [
            'Authorization' => 'Bearer ' . $token,
            'Content-Type' => 'application/json',
        ],
        'query' => [
            'channel' => 'xxxxxxx',
            'oldest' => strtotime('2021-11-01 00:00:00'),
            'latest' => strtotime('2021-11-30 23:59:59'),
        ],
        'timeout' => 120,
    ]);
$responseBody = json_decode(
    json: $response->getBody()->getContents(),
    associative: true);

取得できたようです。
$responseBody['messages']にメッセージが入っています。
$responseBody['messages'][n]['reactions']にリアクションも入っていますね。

#集計用のデータを蓄積
いろんなパターンで集計したいので、いったんMySQLにデータを蓄積します。
Laravelを使っているのでマイグレーションを作ります。

return new class() extends Migration
{
    public function up()
    {
        Schema::create('slack_reactions', function (Blueprint $table) {
            $table->id();
            $table->string('name', 100)->comment('絵文字の名前');
            $table->string('sender_id', 15)->comment('リアクションを行ったユーザのID');
            $table->string('recipient_id', 15)->comment('リアクションをもらったユーザのID');
            $table->string('message_id', 100)->comment('メッセージID');
            $table->dateTime('message_date')->comment('メッセージ日時');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('slack_reactions');
    }
};

続いて、APIで取得したデータを保存していきます。

foreach ($responseBody['messages'] ?? [] as $message) {
    if (!isset($message['user'])) {
        // BOTの発言かな? 発言者のIDがとれないので無視
        continue;
    }
    foreach ($message['reactions'] ?? [] as $reaction) {
        foreach ($reaction['users'] as $reactionUser) {
            $reactions[] = [
                'name' => $reaction['name'],
                'sender_id' => $reactionUser,
                'recipient_id' => $message['user'],
                'message_id' => $message['client_msg_id'] ?? '',
                'message_date' => date('Y-m-d H:i:s', intval($message['ts'])),
            ];
        }
    }
}
DB::table('slack_reactions')->insert($reactions);

ちゃんと保存されましたが、件数がぜんぜん足りません。
マニュアルよく見ると、limitがデフォルトで100でした。
すべて取得したい場合はカーソルを使って取得を繰り返すようです。

/**
 * メッセージ履歴を取得する
 *
 * @param string|null $cursor
 * @return array
 * @throws \GuzzleHttp\Exception\GuzzleException
 */
private function fetchHistories(string|null $cursor): array
{
    $client = new \GuzzleHttp\Client();
    $token = 'xoxp-xxxxx-xxxxx-xxxxx......';
    $response = $client->request(
        method: 'GET',
        uri: 'https://slack.com/api/conversations.history',
        options: [
            'headers' => [
                'Authorization' => 'Bearer ' . $token,
                'Content-Type' => 'application/json',
            ],
            'query' => [
                'cursor' => $cursor,
                'channel' => 'xxxxxxx',
                'oldest' => strtotime('2021-11-01 00:00:00'),
                'latest' => strtotime('2021-11-30 23:59:59'),
            ],
            'timeout' => 120,
        ]);
    return json_decode(
        json: $response->getBody()->getContents(),
        associative: true);
}

メソッド化して、繰り返し呼びます。

$responseBody = $this->fetchHistories(null);
while (count($responseBody['messages'] ?? []) > 0) {
    foreach ($responseBody['messages'] as $message) {
        if (!isset($message['user'])) {
            // BOTの発言かな? 発言者のIDがとれないので無視
            continue;
        }
        foreach ($message['reactions'] ?? [] as $reaction) {
            foreach ($reaction['users'] as $reactionUser) {
                $reactions[] = [
                    'name' => $reaction['name'],
                    'sender_id' => $reactionUser,
                    'recipient_id' => $message['user'],
                    'message_id' => $message['client_msg_id'] ?? '',
                    'message_date' => date('Y-m-d H:i:s', intval($message['ts'])),
                ];
            }
        }
    }
    if (!isset($responseBody['response_metadata']['next_cursor'])) {
        break;
    }
    $responseBody = $this->fetchHistories($responseBody['response_metadata']['next_cursor']);
}

DB::table('slack_reactions')->insert($reactions);

ちゃんと1ヶ月分とれました。

#集計
多く使われたリアクションをSQLで出してみます。
コピペしたいので結果をちょっと整形しています。

SELECT
    CONCAT(ROW_NUMBER() OVER (ORDER BY total DESC), '位') AS number,
    CONCAT(ranking.total, '回') AS total,
    ranking.reaction
FROM (
    SELECT COUNT(*) AS total,
         CONCAT(':', slack_reactions.name, ':') AS reaction
    FROM slack_reactions
    GROUP BY reaction
    ORDER BY total DESC
    LIMIT 10
) AS ranking

#人の名前をとりたい

JOINしたいのでテーブルを用意します。

Schema::create('slack_users', function (Blueprint $table) {
    $table->id();
    $table->string('slack_user_id', 15)->unique()->comment('Slack上でのユーザID');
    $table->string('first_name', 100)->comment('名');
    $table->string('last_name', 100)->comment('姓');
    $table->string('email', 100)->unique()->comment('メールアドレス');
    $table->timestamps();
});

ユーザの取得APIは
https://api.slack.com/methods/users.list
ですね。

#もう一度集計
リアクションを一番もらった人を集計します。

SELECT
    CONCAT(ROW_NUMBER() OVER (ORDER BY total DESC), '位') AS number,
    CONCAT(ranking.total, '回') AS total,
    ranking.username
FROM (
     SELECT COUNT(*) as total,
            CONCAT(recipient.first_name, recipient.last_name) AS username
     FROM slack_reactions
              INNER JOIN slack_users AS recipient ON slack_reactions.recipient_id = recipient.slack_user_id
     GROUP BY username
     ORDER BY total DESC
     LIMIT 10
 ) AS ranking

とれました。名前は出せないですが、開発リーダーがCTOを抑えて1位でした。
わたしですか?6位です。まあまあですね

#さいごに
今回は開発チームのチャンネルでやってみましたが、他にも色々できそうです。
たとえば会社ではCollaというアプリを導入しているのですが、なかなか面白い質問と回答があります。
リモートワークなのでこういった遊びをどんどん盛り込んでコミュニケーションを増やしていきたいですね。

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?