雑コラBotを作ろう⑤: Azure FunctionsからSlackにメッセージを投稿する

Azure Functionsの関数をJavaScriptで作成します。

関数のテンプレートの一覧から「Queue trigger」を選択してJavaScriptで作成を開始します。

関数の作成ペインで関数名とトリガーバインドを設定します。

キューは画像処理関数でメッセージを出力したキューを指定します。

Slackカスタムインテグレーションの設定

Azure Functionsからメッセージを投稿できるようにSlackの設定をします。

SlackのAppディレクトリから「着信Webフック」を検索し、カスタムインテグレーションに追加します。

「着信Webフック」の設定を開き、メッセージを投稿するチャンネルを設定します。その他の項目は任意で設定をします。

「Webhook URL」は、Azure Functionsからメッセージを投稿する際に使用するため、メモにひかえておきます。

スクリプト

スクリプトは以下のように作成しました。

// 1. モジュールの読み込み
const querystring = require("querystring");
const crypto = require("crypto");
const https = require("https")
const url = require('url');

// 2. Slack着信WebフックのURL設定
const slack_url = "https://hooks.slack.com/services/**//**"

// 3. SASトークン発行関数
function generateSasToken(connstring, container, blobName, permissions, effectivehours) {
const xmsversion = "2017-04-17"
var service = "blob"
var sr = service.slice(0 , 1)

// SASトークンクエリの設定
var query_obj = {
"sp": permissions,
"sv": xmsversion,
"sr": sr
};

// SAS開始日時
var startDate = new Date();
startDate.setMinutes(startDate.getMinutes() - 5);
queryobj.st = toutc_datetime(startDate);

// SAS終了日時
if (!effective_hours) {
effective_hours = 24
}
var expiryDate = new Date(startDate);
expiryDate.setHours(startDate.getHours() + effective_hours);
queryobj.se = toutc_datetime(expiryDate);

// 接続文字列からアカウント名・キーを取得
var connectioninfo = querystring.parse(connstring, ";");
connectioninfo.AccountKey = connectioninfo.AccountKey.replace(/\s/g, "+"); //スペースを+に置換
const accountname = connectioninfo.AccountName;
const accountkey = connectioninfo.AccountKey;

// Blob serviceパス生成
if (container.slice(0 , 1) != "/"){
container = "/" + container
};
var canonicalizedresource = '/' + service + '/' + accountname + container + "/" + blobName

// 認証文字列生成
var stringtosign = query_obj.sp + "\n" + //PERMISSION
query_obj.st + "\n" + //START
query_obj.se + "\n" + //EXPIRY
canonicalized_resource + "\n" +
"" + "\n" + //IDENTIFIER
"" + "\n" + //IP
"" + "\n" + //PROTOCOL
query_obj.sv //VERSION);

if (service == 'blob' || service == 'file'){
stringtosign += "\n"
stringtosign += "" + "\n" + //CACHE_CONTROL
"" + "\n" + //CONTENT_DISPOSITION
"" + "\n" + //CONTENT_ENCODING
"" + "\n" + //CONTENT_LANGUAGE
"" //CONTENT_TYPE;
};

// Signature生成
var key = new Buffer(account_key, 'base64')
var hmac = crypto.createHmac('sha256', key);
hmac.update(stringtosign);
query_obj.sig = hmac.digest("base64");

// SASトークン生成
var sastoken = querystring.stringify(queryobj)

var sasurl = "https://" + accountname + ".blob.core.windows.net" + container + "/" + blobName + "?" + sas_token

return {
token: sas_token,
sasUrl: sas_url
};
}

// ISO 8601 format
function toutcdatetime(date){
var Y = date.getFullYear() + '';
var m = ('0' + (date.getMonth() + 1)).slice(-2);
var d = ('0' + date.getDate()).slice(-2);
var H = ('0' + date.getHours()).slice(-2);
var M = ('0' + date.getMinutes()).slice(-2);
//var S = ('0' + date.getSeconds()).slice(-2);
var S = "00";

var utc_date = Y + "-" + m + "-" + d + "T" + H + ":" + M + ":" + S + "Z";
return utc_date;
}

// 4. HTTPリクエスト関数
function https_request(context, options, callback){

// URL解析
var url_parse = url.parse(options.url);

const host = url_parse.host;
var path = url_parse.path;

// メソッド設定
var method = "GET"
if ("method" in options){
method = options.method;
};

// 本文設定
if ("body" in options && typeof(options.body) == "object"){
options.body = JSON.stringify(options.body)
};

// ヘッダ設定
var headers = {}
if ("headers" in options){
headers = options.headers
};
if (options.json){
headers["Content-Type"] = "application/json"
};
headers["Content-Length"] = Buffer.byteLength(options.body)

// オプション
var req_options = {
host: host,
port: 443,
path: path,
method: method,
headers: headers
};

// リクエスト
let req = https.request(req_options, (res) => {
res.setEncoding('utf8');
res.on('data', (chunk) => {

if (options.json && typeof(chunk) == "object") {
chunk = JSON.parse(chunk)
}
callback(null, res, chunk);
});
});

req.on('error', (e) => {
callback(e, "", null);
});

if ("body" in options) {
req.write(options.body);
}

req.end();
};

module.exports = function (context, myQueueItem) {

var payload = {}
if (myQueueItem.status == "ok") {

// 5. SAS URLの生成
var connstring = process.env.recipeteststorageSTORAGE
var sas = generateSasToken(connstring, myQueueItem.container, myQueueItem.blobpath, "r", 24)
var sas_url = sas.sasUrl

// 6. 投稿用メッセージの作成
payload.text = myQueueItem.message + "\n<" + sas_url + "|" + myQueueItem.filename + ">"
}
else {
// 6. 投稿用メッセージの作成
payload.text = myQueueItem.message
}

// 7. HTTPリクエスト
var options = {
url: slack_url,
method: "POST",
json: true,
body: payload
};
httpsrequest(context, options, function(error, response, resbody){
if (!error && response.statusCode == 200){
context.log("Send Message: Succeeded");
context.done();
}
else{
context.log("Send Message: Failed");
context.done();
};
});
};

スクリプトの説明

1. モジュールの読み込み

処理に必要なパッケージを読み込みます。

2. Slack着信WebフックのURL設定

Slackに追加した着信WebフックのURLを設定します。

3. SASトークン発行関数

Blob Storageに保存された画像のパスからSASトークンを生成します。

SASトークン発行に関しては、npm の「azure-storage」というパッケージでもっと簡単に記述できますが、パフォーマンスの観点からBlob Storage APIの使用に沿った関数で実装しています。

4. HTTPリクエスト関数

URLにHTTPリクエストする関数です。

npm の「request」パッケージがNode.jsのHTTPクライアントとして一般的ですが、こちらもパフォーマンスの観点から標準パッケージの「https」でリクエスト用の関数を作成しています。

5. SAS URLの生成

SASトークン発行関数でBlobのSAS URLを生成します。

6. 投稿用メッセージの作成

Slackの着信Webフックのリクエスト形式に合わせてペイロードを作成します。

7. HTTPリクエスト

Slackにメッセージを投稿します。

実行結果

Slackのチャンネルに画像のURLを投稿すると、投稿時刻とほぼ同時に関数のログが表示され、Queue Storageとトリガーにより関数が開始されていることが確認できました。

SlackをみるとBotからメッセージが投稿され、URLの画像と同じ画像が表示されることが確認できました。

SlackをみるとBotからメッセージが投稿され、URLの画像と同じ画像が表示されることが確認できました。

まとめ

今回は、Azure FunctionsからSlackにメッセージを投稿する仕組みを作成しました。これで、Slackに対してAzure Functionsから疑似的に画像を投稿する仕組みが出来上がりました。

あとは、顔に好きな画像を貼り付けてコラ画像を作成する仕組みをすれば雑コラBotの完成です。

次回は、画像処理関数にFace APIを付け加えてコラ画像作成に必要な顔検出の仕組みを追加します。

この記事を書いた人

azure-recipe-user