Slackからesaを検索する

このエントリは イノベーター・ジャパン Advent Calendar 2016 の17日目の記事です。

こんにちは id:hacktk です。
今回はSlack + AWS Lambda + API Gateway + esa のお話をします。

TL;DR

Slackからesaの検索ができました。
f:id:hacktk:20161217090755p:plain

背景

先日、当社で InnoCAFE#23 このやり方であってる?ドキュメント管理と情報共有のお隣事情 というイベントがありまして。
残念ながら私は参加できませんでしたが、社内外でのドキュメント管理などについて大いに盛り上がったようです。
その一方で、社内では「ツールが増えて管理が煩雑になってきている」とみんなが頭を悩ませています。
これについては現在も試行錯誤中で、その一環として「とりあえずSlackからいろいろ検索できるようにしたら?」という話になり、じゃやりましょうかというのが背景となります。

あ、あと esa がつい最近 記事検索を強化した のでAPIも速くなっていそうだなーと思ったのもあります。
なのでとりあえずはesaだけの検索で、他リソースは随時追加していこうかと思っています。

どうやっているか

AWSで楽をします。
SlackのOutgoing webhookからAPI Gatewayを通してAWS Lambdaを叩き、またAPI Gatewayを通ってSlackに戻ってきます。

AWS LambdaにはSlack用のblueprint(雛形)があり、それを使うととても簡単に作れるので使いましょう。
以下の2点にだけ注意すれば、安く早く簡単にbot作れるのでオススメです。

  1. slash commandで実装しようとしてしまうと、実行時間の上限が3000msで泣く
  2. tokenをチェックしないとどこからでも叩き放題のAPIができてしまう

Lambda Function

AWS LambdaのblueprintやAPI Gatewayの作成については、分かりやすい解説エントリがたくさんあるのでそちらを参照してください。
esaのAPIライブラリはrubyのみ公式に用意されていますので、以下のライブラリを使用させていただきました。
ありがとうございます!!

github.com

以下にLambda Functionのソースを載せておきます。

'use strict';

const AWS = require('aws-sdk');
const qs = require('querystring');

const kmsEncryptedToken = process.env.kmsEncryptedToken;
let token;

function processEvent(event, callback) {
    const params = qs.parse(event.body);
    const requestToken = params.token;
    if (requestToken !== token) {
        console.error(`Request token (${requestToken}) does not match expected`);
        return callback('Invalid request token');
    }

    const regExp = new RegExp(params.trigger_word, 'g');
    const searchWords = params.text.replace(regExp, '');

    // get from ENV
    const esaTeam = process.env.esaTeam;
    const esaAccessToken = process.env.esaAccessToken;
    const esa = require('esa-nodejs');
    const client = esa({
        team: esaTeam,
        accessToken: esaAccessToken
    });

    let results = [];
    client.api.posts({q: searchWords}, function(err, res) {
        let posts = res.body.posts;
        for(let i=0,l=posts.length;i<l;i++){
            // post name & url
            results.push(posts[i].full_name);
            results.push(posts[i].url);
            results.push('');
            if (i >= 4) {
                results.push('Your search result ' + l + ' posts over, Top 5 items are displayed.');
                break;
            }
        }

        let text = 'Your search did not match any posts.';
        if (results.length > 0) {
            // code area to make line breaks effective
            text = '```' + results.join("\n") + '```';
        }

        callback(null, {text:text});
    });
}

exports.handler = (event, context, callback) => {
    if (token) {
        // Container reuse, simply process the event with the key in memory
        processEvent(event, callback);
    } else if (kmsEncryptedToken && kmsEncryptedToken !== '<kmsEncryptedToken>') {
        const cipherText = { CiphertextBlob: new Buffer(kmsEncryptedToken, 'base64') };
        const kms = new AWS.KMS();
        kms.decrypt(cipherText, (err, data) => {
            if (err) {
                console.log('Decrypt error:', err);
                return callback(err);
            }
            token = data.Plaintext.toString('ascii');
            processEvent(event, callback);
        });
    } else {
        callback('Token has not been set.');
    }
};

JavaScript力が皆無なのでほとんど雛形のまま・・・

前述の通り、Slackから渡されるtokenをチェックしましょう。 あと kmsEncryptedToken , esaTeam , esaAccessToken は環境変数で渡しています。

まとめ

  • Lambda簡単べんり(初めて触ったのでデバッグだけはつらかった)
  • esaの新検索システム爆速
  • Google DriveやDropboxもAPI使って検索できるようにしたい

ところで

《福岡》健全な環境で思う存分プログラムを書きたいエンジニアを募集中!