#はじめに
この記事はトラストバンクアドベントカレンダー13日目の記事です。
こんにちは!トラストバンクのテックリード 海塩(うしお)(@youhei_seasalt)と申します。
会社として初の試みであるアドベントカレンダーですが、毎日同僚の記事が提供されるというのはとても楽しいですね。
#なにをしたか
最近SlackのAPIを触ることがあったため、リアクションを集計して遊びました。
一番多く使われたリアクションや、一番多くリアクションを受けた人を集計できます。
Slack上でおもむろに↓こんな発言をしようとしています。
GASのほうが親和性が高く、ネット上のサンプルも多いですがPHPerなのであえてPHPでやってみます。
#APIを探す
公式のAPIページを漁ります。
このAPIでメッセージの履歴が取得できそうです。
https://api.slack.com/methods/conversations.history
#APIトークンはどこで?
まずはSlack APIを使えるようにしなければいけません。
いつものように「slack api 使い方」でググります。
いくつかページを見て、Slackアプリを作ればいけそうとわかりました。
早速作ります。
権限はこのくらい付けておけばいいでしょう。
#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というアプリを導入しているのですが、なかなか面白い質問と回答があります。
リモートワークなのでこういった遊びをどんどん盛り込んでコミュニケーションを増やしていきたいですね。