カテゴリー: Slack

AWS Chatbot(beta版)を用いたSlack通知

クラウド事業部 インフラエンジニアの吉岡です。

AWS Chatbot(beta版)を用いたSlack通知を試してみましたので、ご紹介したいと思います。

AWS Chatbotとはどういった機能なのか

Slack チャンネルや Amazon Chime チャットルームに対して通知を行う機能です。
サポートしているサービスの通知をSNSトピックに転送し、
SNSトピックからChatbotを経由して各チャットサービスに表示することができます。
現在対応しているサービスは下記の通りです。

AWS Billing and Cost Managemen
AWS CloudFormation
AWS CodeBuild等の開発者用ツール
Amazon CloudWatch Alarms
Amazon CloudWatch Events
AWS Config
AWS Health
AWS Security Hub
Amazon GuardDuty
AWS Systems Manager

今回はAWS System ManagerのRunCommandを実行し、Slackに通知します。設定する通知フローは下記の図の通りです。

Slackへの通知フロー

手順

・SNSトピックの作成
Amazon SNS→トピック→トピックの作成と進み、適当な名前を付けたトピックを作成します。(サブスクリプションはまだ設定しません。)

f:id:seeds-std:20191114181702p:plain

・CloudWatchEventの設定
CloudWatch→ルールの作成、と進みルールを作成します。
今回はchatbot-testという名前で作成しました。

サービス名:EC2 Simple Systems Manager(SSM)
f:id:seeds-std:20191114182349p:plain

イベントタイプ:すべてのイベント
f:id:seeds-std:20191114182353p:plain

ターゲット:SNSトピック(先ほど作成したトピック)
f:id:seeds-std:20191114182357p:plain

・Chatbotのインストール
AWS Chatbotを選択し、
Chatbotの設定を始めていきます。
通知を送る先となるChatツールを選択します。

f:id:seeds-std:20191114182950p:plain

SlackにChatbotをインストールします。

f:id:seeds-std:20191114183017p:plain

今回はpublicのslacktestというチャンネルに通知するように設定します。
IAMロールは自動作成、ロール名はchatbot-slackとしました。
SNS regionはSNSトピックの設定されているリージョン(今回は東京リージョン)
SNS topicsは先ほど作成したchatbot-testを指定します。

f:id:seeds-std:20191114183152p:plain
f:id:seeds-std:20191114183155p:plain
f:id:seeds-std:20191114183157p:plain

設定が完了すると、SNSトピックにサブスクリプションが追加されていることが確認できます。

動作確認

RunCommandを実行し、Slackに通知されることを確認します。
今回はAWS-RunShellScriptでechoコマンドを実行します。
インスタンス指定
SNS通知なし
の条件で下記の内容で実行します。

#!/bin/bash
echo "Hello chatbot"

実行後に下記の様な通知がSlackに届いていることが確認できました。

f:id:seeds-std:20191114183614p:plain

あとがき

今回はChatbotを用いてSlack通知を行うために必要な作業がどういったものか、勉強を兼ねて試してみましたが、非常に簡単に設定ができました。
現在はbeta版ですので、一部の環境のみですが、シーズではこの機能を利用してAWS Health Dashboardの情報をslack通知する設定を行っています。

CloudTrailが有効化されていることが前提ですが、セッションマネージャーを使ったログイン時に通知が届くような設定もできるようです。
正式なリリースが楽しみですね!

SlackAPIを使って簡単なTODOチェックアプリを作成してみた

WEBエンジニアの石田です。
さて、僕は前回もSlackネタでしたが、今回もSlackネタです。

弊社では、お掃除部という部活(?)がありまして、拭き掃除・ゴミ出し・換気などのオフィス内の掃除、あと朝一のコーヒー作りを有志が毎日行っています。
当番とかは決まっておらず、できるメンバーでやろう!というスタンスなのですが、部員の大半を占めるエンジニア達はフレックス制で出社時間がバラバラ…
となると、チェックリストは欲しいですよね。

紙とかホワイトボードでチェック!となるのがまあ普通だと思いますが、弊社はIT企業。そして社内のコミュニケーションにSlackを使ってるんだから、せっかくならオンラインで済ませてしまいたい。という欲が僕の中で沸々と湧き上がってきたので、SlackAPIを使ってアプリ作りました(・ω・)b

まずは出来上がったものをご紹介します。

f:id:seeds-std:20190425000202g:plain
ボタンをクリックすると名前が登録され、同じボタンを押すと登録が解除されます

仕組み

  • AM9:00になるとタスク一覧と、項目ごとの絵文字のボタンが並んでいるメッセージが自動投稿される。
    • cronで平日毎日9:00にpostするAPIを叩く
  • タスクの絵文字ボタンをクリックすると、クリックしたユーザーのIDがタスク一覧の下に表示される。
  • 既にボタンをクリック済みのユーザーが、もう一度同じ絵文字のボタンをクリックするとタスク一覧の下に表示されていた名前が消える。
    • 絵文字のボタンを押すとSlack から指定したURLにリクエストが送られ、リクエストに応じて処理し、メッセージを書き換えるAPIを叩く

今回つかったもの

  • PHP
  • MySQL
  • Slack API
    • Interactive Components

やってみる

1. Slack APIでAppを作成

  • Interactive Components
    メッセージにボタンやメニューなどを付与し、ユーザーからのアクションに応じて応答できる機能です。
    GitHubやBitBucketのAppをはじめ、最近とても頻繁に使われているので多分見たことあるのではないでしょうか。

https://api.slack.com/apps?new_app=1

f:id:seeds-std:20190610212654p:plain
AppNameは後から変更も可能でした。

上記のURLにアクセスすると、SlackAppの作成画面が出るので AppName (アプリ名) と Development Slack Workspace (使用するSlackのワークスペース) を指定し、 Create App します。
SlackでAPIを作成すると、FeaturesにInteractive Componentsという項目があり、Onにすると設定が可能になります。
f:id:seeds-std:20190610213057p:plain
設定は色々とありそうですが、今回は Request URLに任意のURL( example.com/api/hogehoge.php など)を貼ればOK。

あと、投稿用にSlackのトークンを取得しておきます。
OAuth & Permissions からScopesを絞って Install App。 Permission Scopeはchat:write:botを選択でOKです。

f:id:seeds-std:20190725210251p:plain
権限はchatだけでok
f:id:seeds-std:20190725210349p:plain
Permissionを設定したらInstall App

これでSlack上の設定は完了。

2. サーバー側の設定

とりあえずmysqlでDBを用意。型は適当につけてます

CREATE TABLE polls
(
id          INT AUTO_INCREMENT PRIMARY KEY,
message_ts  VARCHAR(255) NOT NULL,
channel_id  VARCHAR(255) NOT NULL,
text        TEXT         NOT NULL,
answers     TEXT         NOT NULL,
attachments TEXT         NOT NULL,
created_at  TIMESTAMP    NULL
) DEFAULT CHARSET = utf8 AUTO_INCREMENT = 1;
CREATE TABLE votes
(
id          INT AUTO_INCREMENT PRIMARY KEY,
channel_id   VARCHAR(255) NOT NULL,
message_ts   VARCHAR(255) NOT NULL,
user_name    VARCHAR(255) NOT NULL,
user_id      VARCHAR(255) NOT NULL,
action_name  VARCHAR(255) NOT NULL,
action_value VARCHAR(255) NOT NULL,
created_at   TIMESTAMP    NOT NULL
) DEFAULT CHARSET = utf8 AUTO_INCREMENT = 1;

貼り付けた任意のURLではなく、まずpublicの外にでも投稿用のファイルを作成します。
postの第一引数の連想配列が選択肢です。nameに表示する文字を入れて、 iconはslackのアイコンの名前を入力(::はとる)でOK。なくてもOK。

<?php
post([
['name' => '換気',    'icon' => 'wind_blowing_face'],
['name' => 'ゴミ出し',    'icon' => 'wastebasket'],
['name' => '拭き掃除',   'icon' => 'sparkles'],
['name' => 'コーヒー', 'icon' => 'coffee'],
] , '今日のTODO');
function post($questions, $description) {
$attachments = [];
$buttons = [];
$text = $description . "\n";
// answersが表示テキスト、buttonsが実施ボタンになる
$answers = [];
foreach ($questions as $key => $button) {
$answers[] = ':' . $button['icon'] . ': ' . $button['name'];
$buttons[] = [
'name' => $button['icon'] ?? ($key + 1),
'text' => ':' . ($button['icon'] ?? ($key + 1)) . ':',
'type' => 'button',
'value' => $button['name']
];
}
// ボタンは5つを超えるとattachmentがスマホで表示できなくなるためを分割しておく
$block = array_chunk($buttons, 5);
// タスクごとに改行する
$text .= implode("\n", $answers);
// ボタンのブロックごとにattachmentを作成する
foreach ($block as $key => $action) {
$attachments[] =
[
'fallback' => 'daily' . $key,
"callback_id" => "daily",
"color" => "#3AA3E3",
"attachment_type" => "default",
'actions' => $action
];
}
// Slackに投稿を行う
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_URL => 'https://slack.com/api/chat.postMessage',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json; charset=utf-8',
'Authorization: Bearer ' . '【SLACKのトークン】'
],
CURLOPT_POSTFIELDS => json_encode([
'channel' => '【投稿チャンネル】',
'text'    => $text,
'attachments' => $attachments
])
]);
$response = curl_exec($curl);
$response = json_decode($response, true) ?? null;
if ( !$response ) {
return false;
}
//投稿情報をMySQLに保存しておく
$db = new PDO(
'mysql:host=' . '【接続するホスト】' .';dbname=' . '【DB名】' . ';charset=utf8',
'【DBユーザー名】', '【DBパスワード】', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$query = $db->prepare('INSERT INTO polls (message_ts, channel_id, text, answers, attachments, created_at) VALUES (:message_ts, :channel_id, :text, :answers, :attachments, :created_at)');
$query->execute([
':message_ts'  => $response['ts'],
':channel_id'  => $response['channel'],
':text'        => $description,
':answers'     => json_encode($answers),
':attachments' => json_encode($response['message']['attachments']),
':created_at'  => date('Y-m-d H:i:s')
]);
return $response;
}

上記のPHPファイルを実行すると指定したチャンネルに下記のようなフォームみたいなものが投稿されます。

f:id:seeds-std:20190809175228p:plain

各ボタンをクリックすると、先程のURLのhoge.phpにPOSTが走るので、下記を配置しておきます。

<?php
if (!empty($_POST['payload'])) {
$vote = json_decode($_POST['payload'], true);
$message = $vote['message_ts'];
$channel = $vote['channel']['id'];
$user_id = $vote['user']['id'];
$username = $vote['user']['name'];
$db = new PDO(
'mysql:host=' . '【接続するホスト】' .';dbname=' . '【DB名】' . ';charset=utf8',
'【DBユーザー名】', '【DBパスワード】', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$query = $db->prepare('SELECT * FROM polls WHERE message_ts = :message_ts AND channel_id = :channel_id');
$query->execute([
':message_ts'   => $message,
':channel_id'   => $channel
]);
$data = current($query->fetchAll());
if ($data) {
$attachments = json_decode($data['attachments'], true);
//既に存在する実施TODO・実施者IDとボタン押した人の実施TODO・IDが一致したら存在フラグをたてる
$votes = selectVotes($db, $message, $channel);
$exist = false;
foreach ($votes as $answer) {
if ($answer['user_id'] === $user_id && $answer['action_value'] === $vote['actions'][0]['value']) {
$exist = $answer['id'];
}
}
//存在フラグが立ってる場合は削除・ない場合は新規挿入
if ($exist) {
$db->prepare('DELETE FROM votes WHERE id=:id')->execute([':id' => $exist]);
} else {
$db->prepare('
INSERT INTO
votes
(channel_id, message_ts, user_name, user_id, action_name, action_value, created_at)
VALUES
(:channel_id, :message_ts, :user_name, :user_id, :action_name, :action_value, :created_at)')
->execute([
':channel_id'   => $channel,
':message_ts'   => $message,
':user_name'    => $username,
':user_id'      => $user_id,
':action_name'  => $vote['actions'][0]['name'],
':action_value' => $vote['actions'][0]['value'],
':created_at'   => date('Y-m-d H:i:s')
]);
}
// 投稿したTODOの現在の実施者一覧を出す
$votes = selectVotes($db, $message, $channel);
$result = [];
$answers = json_decode($data['answers']);
foreach ($answers as $answer) {
$result[$answer] = '';
foreach ($votes as $vote) {
if ($answer === ':' . $vote['action_name'] . ': ' . $vote['action_value']) {
$result[$answer] .= ' <@' . $vote['user_id'] . '>';
}
}
}
// 実施者一覧をメッセージに反映する
$text = $data['text'];
foreach ($result as $answer => $voters) {
$text .= "\n" . $answer . "\n";
$text .= empty($voters) ? '' : $voters . "\n";
}
$update = [
'channel' => $data['channel_id'],
'ts' => $data['message_ts'],
'text' => $text,
'attachments' => $attachments
];
// Slackのメッセージを更新する
$url = 'https://slack.com/api/chat.update';
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json; charset=utf-8',
'Authorization: Bearer ' . '【SLACKのトークン】'
],
CURLOPT_POSTFIELDS => json_encode($update)
]);
return curl_exec($curl);
}
}
function selectVotes(PDO $db, string $message_ts, string $channel_id)
{
$query = $db->prepare('
SELECT
*
FROM
votes
WHERE
message_ts = :message_ts AND
channel_id = :channel_id
ORDER BY
action_value
');
$query->execute([
':message_ts'   => $message_ts,
':channel_id'   => $channel_id
]);
return $query->fetchAll();
}

送られてきたpostの中にユーザーID・ボタンを押したmessageの情報・押したボタンの情報があるので、それを照合し、あれば削除(MySQLから既存レコードをDELETE)・なければ作成(MySQLにINSERT)し、元のメッセージを状況に合わせて更新することで投票を実現しています。

所感

プログラムの部分で長くはなってしまいましたが、これでTODOアプリは完成です。シーズでは毎日の日次タスクと、月曜日に週次タスクを投稿する形で運用してますが、今のところ問題なく稼働しています。
こういったTODOリストの共有やリマインダ、GitHub/BitBucketの連携など、SlackAPIを応用することで、手軽に様々な機能を日々のコミュニケーションの中に自力で追加できるのはとても魅力的だと感じました。

アイデア次第ではもっと面白いことができそうなSlackAPI、もっと色々な活用方法を見出して社内に還元していきたいなあ…と目論んでおります!

AWS IoT Enterprise Buttonを使ってSlackに通知を送ってみた

はじめまして!18年度新卒・WEBエンジニアの石田です。

弊社のコーヒーメーカーがリニューアルされ、コーヒーを淹れる機会がすごく多くなりました。
しかし、コーヒーメーカーができてから30分で保温が切れる設定で、
・追加で保温ができない…
・どうせならできたてが飲みたいけど、できたタイミングが分かりにくい…
・かといっていちいちSlackにコーヒーできましたって書くのもめんどくさい!
→自動投稿させたい!
→→ボタンワンクリックで済ませたい!

ということで、会社にAmazon「AWS IoT Enterprise Button」を買ってもらい、折角なのでクリエイターズブログに投稿することにしました!

f:id:seeds-std:20181105211732j:plain
新コーヒーメーカーと「AWS IoT Enterorise Button」

新コーヒーメーカーと「AWS IoT Enterorise Button」

AWS IoT Enterorise Button

ワンプッシュで商品が注文できる!と話題になった Amazon Dash Buttonをベースにした、自由にプログラムを動作させることのできるボタンです。
ボタンのクリックをトリガーに、AWSのサービスに接続できます。すごい。

f:id:seeds-std:20181105212349j:plain
ちっちゃいです

モバイルアプリを使ってWiFiの設定ができ、ステータスも確認可能。

f:id:seeds-std:20181105213458p:plainf:id:seeds-std:20181105221850p:plain
iOSアプリ「AWS IoT 1-Click」

何度か設定してみたのですが、デバイスリージョンはオレゴン一択なのでしょうか。東京リージョンに変更できませんでした。

そこからAWS IoT 1-Clickというサービスを選択し、プロジェクトを作成することでデバイスの動作を定義することができます。

選択できるのは
・SMSの送信
・Eメールの送信
・Lambda関数の選択
Slackに自動投稿したかったのでLambda一択でしたが、SMS・Eメール送信も色々使えそうで良いですね。

あとはLambdaでjavascriptを使ってwebhookにPOSTすれば投稿完了!

exports.handler = function(event, context) {
const slack = (params) => {
let https = require('https');
let host = 'hooks.slack.com';
let data = JSON.stringify({"text": params['message']});
let options = {
hostname: host,
port: 443,
path: params['path'],
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
};
let req = https.request(options, (res) => {
console.log('status code : ' + res.statusCode);
res.setEncoding('utf8');
res.on('data', (d) => {
console.log(d);
});
});
req.on('error', (e) => {
console.error(e)
;});
req.write(data);
req.end();
};
var post = function() {
return slack({
path: 'hogehogehogehogehoge',
message: 'コーヒーができました:coffee:'
});
};
setTimeout(post, (60 * 6 * 1000));
};

f:id:seeds-std:20181105221032p:plain
アイコンはデザイナー・コウノ氏が作ってくれました!かわいい

6分で完成するので、その分だけタイムアウトさせるようにしています。
起動時間は5分までだと思ってたからどうしようかな…と思っていたのですが、15分までいけるようになっていたのが驚きでした。

IoT ButtonやLambdaを触ったのははじめてだったので時間がかかりましたが、セットアップからSlackに投稿できるようになるまで1時間ほど。すごく簡単でした。
他にもいろいろな使い方ができそうですね。シーズでは早速、更に2台のIoT Buttonを追加購入しました(笑)
社内でアイデアを募ってどういう使い方をしようかと思案中です!

© SEEDS Co.,Ltd.