Fusic Tech Blog

Fusion of Society, IT and Culture

connpass APIを使ってサーバーレスに社員のイベント参加を見える化(Slack通知)した話
2019/11/13

connpass APIを使ってサーバーレスに社員のイベント参加を見える化(Slack通知)した話

Fusicでは現在社内の色々なリソースを「見える化」しようとしています。

そのうちの一つとして、社員の主催イベント・参加イベントをSlack通知で「見える化」しました。

具体的には、connpass APIから取得したデータを用いて、Slack通知を作成しました。

ただ、connpass APIはイベント情報が取得できるだけで、イベントが作成された時のイベントなどは取得できないため、

  • 定期的にイベントを取得
  • 差分を通知

という形で実装をしました。

今回はその過程を記事にできたらと思います。

最終的に出来るもの

Slackで社員の主催・参加するconnpassのイベントが通知されるようになります。

Slack通知の画像

使用ツール

  • connpass API
  • AWS Lambda
  • Google Spreadsheet
  • Slack Webhook API
  • Amazon Cloud Watch Events
  • Amazon S3

仕様言語

  • Google Apps Script
  • Node.js

社員のconnpassユーザー名を集める

まずは社員のconnpassユーザー名を集める必要があります。

今回は新しく社員が増えることも考慮して、Google Spreadsheetに各自で記入してもらいました。

Spreadsheetの画像

ちなみにconnpassのユーザー名はマイページから確認ができます。

connpassのユーザー名の画像

ニックネームが上にあり、()に入っている方がユーザー名なのでご注意を。

Spreadsheetをconnpassユーザー取得用APIとして利用する

次に、入力してもらったシートを使って、connpassユーザーを取得するAPIを作成します。

GASを使って以下のコードを記述します。

function getData(sheetName) {
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  const rows = sheet.getDataRange().getValues();
  const keys = rows.splice(1, 0)[0];
  const result = rows.map(function(row) {
    var array = [];
    row.map(function(item, index) {
      array += String(item);
    });
    return array;
  });
}

function doGet() {
  const data = getData('シート1');
  return ContentService.createTextOutput(JSON.stringify(data, null, 2))
  .setMimeType(ContentService.MimeType.JSON);
}

アプリを公開して、API化します。

発行されたURLにアクセスすると

取得したユーザー名の配列

のように、JSONの配列でconnpassのユーザー一覧が取得できます。

社員の主催・参加イベントの一覧を取得する

次に、作成したAPIのユーザー名を使って、社員の主催・参加イベントの一覧を取得します。 今回は、AWSのCloud Watch Eventを使って定期的に実行することを想定しているため、 AWS Lambda上にコードを記述していきます。

ここではconnpass APIを利用します。 connpass APIの仕様は、こちらに詳しく書いてあります。

以下のコードで、社員の主催・参加イベントを取得します。

const request = require('request')
const url = "https://script.google.com/macros/***********" // 作成したconnpassユーザー名を取得するURL
const connpassAPIUrl = 'https://connpass.com/api/v1/event'

exports.handler = (event, context, callback) => {
    request({
        json: true,
        url: url
    }, (err, res, body) => {
        const connpassUsers = body

        // 参加者・管理者がユーザー名に一致するイベントを開催日時順に20件取得するURL
        const connpassAPI = connpassAPIUrl + "?order=2&count=20&nickname=" + connpassUsers + "&owner_nickname=" + connpassUsers
        request({
            json: true,
            url: connpassAPI,
            headers: {
                'Content-Type':'application/json',
                'User-Agent': 'Node/10'
            }
        }, (err, res, body) => {
            console.log(body)
        })
    })
}

bodyの中には、一致するイベントが20件取得されます。

取得したイベントをS3バケットに保存する

AWSのストレージサービスであるS3のバケットを作成します。

作成後、取得したイベントのIDをS3バケットに保存します。

S3へ保存するコードを、先ほどのコードに追記します。

const request = require('request')
const url = "https://script.google.com/macros/***********" // 作成したconnpassユーザー名を取得するURL
const connpassAPIUrl = 'https://connpass.com/api/v1/event'

const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const BucketName = '作成したバケット名'
const Key = 'events.json'

exports.handler = (event, context, callback) => {
    request({
        json: true,
        url: url
    }, (err, res, body) => {
        const connpassUsers = body

        // 参加者・管理者がユーザー名に一致するイベントを開催日時順に20件取得するURL
        const connpassAPI = connpassAPIUrl + "?order=2&count=20&nickname=" + connpassUsers + "&owner_nickname=" + connpassUsers
        request({
            json: true,
            url: connpassAPI,
            headers: {
                'Content-Type':'application/json',
                'User-Agent': 'Node/10'
            }
        }, (err, res, body) => {
            const eventIds = res.body.events.map((event) => event.event_id) // 取得したイベントからIDのみの配列を作成
            const params = {
                Bucket: BucketName,
                Key: Key,
                Body: JSON.stringify(eventIds)
            };
            // S3に保存
            s3.putObject(params, function(err, data) {
                context.done()
            })
        })
    })
}

これを実行すると、S3に取得したイベントのIDが保存されます。

保存されたイベントのIDと取得したイベントのIDの差分をとり、Slackに通知する

最後に、イベントの作成を検知するために、S3に保存されたIDと取得したIDの差分をとり、Slackに通知します。

最終的なコードは以下になります。

const request = require('request')
const url = "https://script.google.com/macros/***********" // 作成したconnpassユーザー名を取得するURL
const connpassAPIUrl = 'https://connpass.com/api/v1/event'

const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const BucketName = '作成したバケット名'
const Key = 'events.json'

const slackUrl = 'https://hooks.slack.com/services/' // SlackのWebhookのURL
const dayjs = require('dayjs') // yarn add dayjsでインストール。ZIPにしてLambdaにアップロード

exports.handler = (event, context, callback) => {
    request({
        json: true,
        url: url
    }, (err, res, body) => {
        const connpassUsers = body

        // 参加者・管理者がユーザー名に一致するイベントを開催日時順に20件取得するURL
        const connpassAPI = connpassAPIUrl + "?order=2&count=20&nickname=" + connpassUsers + "&owner_nickname=" + connpassUsers
        request({
            json: true,
            url: connpassAPI,
            headers: {
                'Content-Type':'application/json',
                'User-Agent': 'Node/10'
            }
        }, (err, res, body) => {
            const newEventIds = res.body.events.map((event) => event.event_id) // 取得したイベントからIDのみの配列を作成

            // S3からイベントIDのデータを取得
            s3.getObject({
                Bucket: BucketName,
                Key: Key
            }, function (err, data) {
                const oldEventIds = JSON.parse(data.Body.toString())

                // イベントIDの差分を取得
                const diff = getArrayIndexValueDiff(oldEventIds, newEventIds)

                // 差分があった場合
                if (diff.length > 0) {
                    // イベントIDごとにSlack通知を送る
                    diff.forEach((event_id) => {
                        request({
                            json: true,
                            url: connpassAPIUrl + '?event_id=' + event_id,
                            headers: {
                                'Content-Type':'application/json',
                                'User-Agent': 'Node/10'
                            }
                        }, (err, res, body) => {
                            const event = body.events[0]
                            const options = getSlackOptions(event)
                            request.post(options, function (err, res, body) {
                                console.log('イベントを通知しました')
                            })
                        })
                    })
            const params = {
                      Bucket: BucketName,
                      Key: Key,
                      Body: JSON.stringify(newEventIds)
                    };
                    s3.putObject(params, function(err, data) {
                      callback(null, '変更をS3に反映しました')
                    })
                } else {
                    // 差分がなかった場合
                    callback(null, '変更はありません')
                }
            })
        })
    })
}

// イベントIDの差分を取得
function getArrayIndexValueDiff(oldArr, newArr) {
    return newArr.filter((v, i)=> !oldArr.includes(v));
}

// SlackのWebhookのオプションを生成
function getSlackOptions(event) {
    return {
        url: slackUrl,
        headers: {
          'Content-type': 'application/json'
        },
        body: {
          "attachments": [
              {
                  fallback: "イベントが作成されました",
                  pretext:  "イベントが作成されました",
                  color: "#D00000",
                  fields: [
                    {
                        title: event.title,
                        value: event.event_url
                    },
                    {
                        title: "開催日時",
                        value: dayjs(event.started_at).format('YYYY/MM/DD HH:mm') + "~" + dayjs(event.ended_at).format('YYYY/MM/DD HH:mm')
                    },
                    {
                        title: "場所",
                        value: event.place
                    }
                  ]
              }
          ]
        },
        json: true
    };
}

これを保存し、Amazon Cloud Watch Eventで1時間に一回関数を実行します。

実行結果

以下のようにSlack通知がくれば、完成です。

Slack通知の画像

つくってみて

会社のメンバーが主催しているイベントで知らなかったイベントを知ることができたり、 意外なメンバーが意外なイベントに参加していることを知ることができたり、

社内の知らなかったことが可視化できて面白かったです。

是非皆さんの会社でも試してみてください!

最後まで読んでいただき、ありがとうございました。

yoshino

yoshino

Fusicエンジニア。Webアプリケーションやブロックチェーンプログラミングをしています。PHP, Vue.js, Solidity etc..