3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

IBM Cloud Functions でLINEとSlack連携させてみた

Last updated at Posted at 2020-08-11

はじめに

この記事は、LINEとIBM Cloud Functionsを連携したSlackへの通知機能を作ったので、3回に分けて紹介したい記事の第3回になります。
 第1回:IBM Cloud Functons 動かしてみた
 第2回:IBM Cloud FunctionsでNode.jsのパッケージを利用してみた
 第3回:IBM Cloud Functions でLINEとSlack連携させてみた ← この記事

なんでLINEとSlackを連携させようと??

近年、自社の社内コミュニケーションツールがSlackに移行したのですが、それ以前からチームメンバーの他愛のないやり取りをLINEで行っていました。協業するベンダーさんも含まれており、全員がSlackに移行するのは面倒(招待は本来可能)と思い、しかし現場を管理するマネージャーをLINEグループに招待したいかというと別の話。バカなやり取りを見せたいとは思わず、ただ、急な勤怠連絡が入るとLINEにいるメンバーは知っているけど、マネージャーが把握していないという状況は避けたい。結果、LINEで勤怠連絡だけ、社内のSlackに通知したら良いのではないかと思い、作りました。

必要なもの

  • LINEアカウント
  • Slackの目的のチャンネルに投稿可能なInoming WebhookのURL

何はともあれ、IBM Cloud Functionsの受け口を作成

LINEと連携する前に、LINEのメッセージを受取るFunctionsのAPIを作成しましょう。
前々回、前回の記事を読まれた方は余裕なはずですが、簡単に。
Functionsのトップからアクションを選んで、作成を選択しましょう。
image.png
作成対象でアクションを選択したら、アクション名を入力し、 ランタイムを選択、 作成を選択してください。
image.png
1点だけ、自動生成されたコードに、呼び出された時のparamsログを出力するように1行追加しておいてください。
image.png

コード
console.log(JSON.stringify(params));

APIとして公開するために、Functionsのトップから、APIを選択して、以前作成したAPIを選択します。
APIが表示されたら定義および保護を選択し、操作の作成から新しいAPIを作成しましょう。
image.png
LINEからPOSTされるので、verbPOSTを選択するように注意してください。あとは、適当に入力して、作成します。
image.png
これで、画面下部の保存を選択すれば、APIが公開されます。

LINE側作業

LINE Developers

LINEでアプリ開発するなら必ず必要な LINE developers のサイトがあるので、ご自身のラインアカウントでログインしてください。
image.png
ログインしたら、こんな感じの画面が出るので、Create でProviderを作成しましょう。
適当な名前を入れてCreateしましょう。
image.png
Create a Messaging API channelを選択します。
image.png
各種項目を入力して、Createを選択しましょう。
Channel type:そのまま
Provider:そのまま
Channel icon :そのまま
Channel name:かたひろのSlack通知 ※入力必須 マルチバイト文字推奨
Channel description:katahiro-qiita ※入力必須
Category:個人 ※入力必須
Subcategory:個人(その他) ※入力必須
Email address:※入力必須
Privacy policy URL:不要
Terms of use URL:不要
image.png
このようにChannelが無事に作成できました。
image.png

Messaging APIの設定

各種設定をしてきます。
image.png
Webhook URLに先ほど作成したFunctionsのAPIのURLを設定します。
image.png
最下部にあるChannel access tokenIssueを選択して発行しておいてください。後ほど利用します。
image.png

動作検証

では、これで簡易な設定は完了したので、画面中ほどにあるQRコードをLINEで読み込んで、友達になりましょう。
では、「テスト」とメッセージを送ってみます。
image.png
自動応答の設定が残っているので、何かしらメッセージが返ってきますが、ここでは無視します。
ここで、IBM Cloud Functionsにはどのようなメッセージが届いているのでしょうか?
Functionsのトップから、モニターを選択すると、動作したAPIの情報が参照できます。
確認すると、LINEに「テスト」と送った時間に、アクティビティーログが出力されています。
image.png
ここで、今回のアクティビティーログを開くと、アクション作成時に、console.log(JSON.stringify(params))を記述したメリットが出てきます。そう、LINEから受け取った全量がどのようなものなのか確認できます。
image.png
ヘッダー情報は無視して、LINEから受け取ったeventsに限定してお見せすると、このような情報を受取っています。

{
  "events":[
    {
      "message":{
        "id":"12457265946945",
        "text":"テスト",
        "type":"text"
      },
      "mode":"active",
      "replyToken":"2af72141cd3f42319f30edd7e87dbc94",
      "source":{
        "type":"user",
        "userId":"U19ca3e54ebdd50e1fa26dxxxxx"
      },
      "timestamp":1596792352030,
      "type":"message"
    }
  ]
}

大事なのが、以下です。
text:もちろんLINEで投稿されたメッセージ
replyToken:応答メッセージを返す場合はこのTokenが必要になります
userId:LINEのAPIを利用して、メッセージを送ったユーザー名を取得する場合、API呼出しに必要になります

LINEで応答

LINE developers側のMessaging API Settingsの画面下部に、下記のLINE Official Account featureの設定項目があるので、右側のEditを選択します。
image.png

下記のようなLINE Official Account Managerの画面が表示されます。
image.png

画面左部の応答設定のメニューから、あいさつメッセージオフに、応答メッセージオフにしましょう。これで、友達になった時に自動でメッセージが送られたり、メッセージを送ると自動で応答される、ということが無くなります。
image.png
このように、自動応答メッセージも返答されなくなりました。
image.png
さて、オウム返しのように、メッセージ内容を返してくれるように、アクションのコードを行いましょう。

package.json
{
  "name": "qiita-action",
  "version": "1.0.0",
  "main": "action.js",
  "dependencies": {
    "request": "^2.88.0"
  }
}

注意点としては、LINEのAPIを実行する時は、リクエストのヘッダーにAuthorization:Bearerを設定して、先ほどのChannel access tokenを指定するようにしてください。

action.js
const main = async ( params ) => {
  // bearer には Channel access tokenを設定する
  const bearer = "HW9WPp3A33xxxxxxx"
  const lineReplyApiUrl = "https://api.line.me/v2/bot/message/reply";

  // LINEから来ている場合にのみ限定
  if(params.events && params.events[0].type === "message" ){
    
    // LINE情報取得
    const message = params.events[0].message.text;
    const replyToken = params.events[0].replyToken;
    const userId = params.events[0].source.userId;

    // LINE ユーザー名取得(1対1のトーク用)
    const getUserNameUrl = "https://api.line.me/v2/bot/profile/" + userId;
    const getUserNameOption = {
      method : "GET",
      url : getUserNameUrl,
      headers : {
        "Authorization" : "Bearer " + bearer,
        "Content-Type": "application/json",
      }
    }

    const userLineAPIObj = await callAPI(getUserNameOption);
    const userNameObj = JSON.parse(userLineAPIObj.body);
    const userName = userNameObj.displayName;
    
    // LINE返信
    const lineMessage = {
      "type" : "text",
      "text" : `${userName}さん、「${message}」と言いましたね`
    }
    const lineReplyOption = {
      method : "POST",
      url : lineReplyApiUrl,
      headers : {
        "Authorization" : "Bearer " + bearer,
        "Content-Type"  : "application/json",
      },
      json : {
        messages :[ 
          lineMessage
        ],
        replyToken : replyToken,
      }
    }
    const replyLineAPIObj = await callAPI(lineReplyOption);
    return true;
  }
  return true
}

const callAPI = (httpOption) => {
  return new Promise((resolve,reject) => {
    const request = require( 'request' );
    request( httpOption, ( err, res, buf ) => {
      if( err ){
        reject( { status: false, error: err } );
      }else{
        resolve(res);
      }
    });
  })
}

exports.main = main;

お見せ出来ませんが、私のLINEの登録名を取得した上で、取得したメッセージ内容も応答に含められていることが確認できました。
image.png

Slackへ通知

後は、勤怠連絡の場合のみ、Slackに通知するように書き換えるだけです。

action.js
const main = async ( params ) => {
  // bearer には Channel access tokenを設定する
  const bearer = "HW9WPp3A33xxxxxxx"
  const lineReplyApiUrl = "https://api.line.me/v2/bot/message/reply";
  const incomingWebhookUrl = "https://hooks.slack.com/services/xxx/xxx/xxxxx";

  // LINEから来ている場合にのみ限定
  if(params.events && params.events[0].type === "message" ){
    // LINEメッセージを取得
    const message = params.events[0].message.text;
    // 勤怠連絡扱いのワードが含まれているかチェック
    if (message.match(/勤怠連絡/)){
      // LINE情報取得
      const replyToken = params.events[0].replyToken;
      const userId = params.events[0].source.userId;

      // LINE ユーザー名取得(1対1のトーク用)
      const getUserNameUrl = "https://api.line.me/v2/bot/profile/" + userId;
      const getUserNameOption = {
        method : "GET",
        url : getUserNameUrl,
        headers : {
          "Authorization" : "Bearer " + bearer,
          "Content-Type": "application/json",
        }
      }

      const userLineAPIObj = await callAPI(getUserNameOption);
      const userNameObj = JSON.parse(userLineAPIObj.body);
      const userName = userNameObj.displayName;

      // Slack通知
      const slackMessage = {
        "text"       : "[ LINE通知 ] " + message,
        "channel"    : "Cxxxxx",
        "username"   : userName,
        "icon_emoji" : ":warning:",
      }
      
      const slackSendOption = {
        method: 'POST',
        url: incomingWebhookUrl,
        encoding: null,
        headers: {
          "Content-type": "application/json",
        },
        json: slackMessage
      };
      const slackResult = await callAPI(slackSendOption);

      // LINE返信
      const replyText = (slackResult.statusCode === 200) ? "Slackに通知しました" : "Slack連携に失敗しました";
      const lineMessage = {
        "type" : "text",
        "text" : replyText,
      }
      const lineReplyOption = {
        method : "POST",
        url : lineReplyApiUrl,
        headers : {
          "Authorization" : "Bearer " + bearer,
          "Content-Type"  : "application/json",
        },
        json : {
          messages :[ 
            lineMessage
          ],
          replyToken : replyToken,
        }
      }
      await callAPI(lineReplyOption);
      return true;
    }
    return true
  }
}

const callAPI = (httpOption) => {
  return new Promise((resolve,reject) => {
    const request = require( 'request' );
    request( httpOption, ( err, res, buf ) => {
      if( err ){
        reject( { status: false, error: err } );
      }else{
        resolve(res);
      }
    });
  })
}

exports.main = main;

こんな感じで、勤怠連絡がメッセージに含まれる場合だけ、反応していることがわかります。
image.png
Slackのチャンネルを確認すると、通知が飛んでいることが確認できます。
image.png

グループトークへの対応

目的通りに動いたことを確認しましたが、いざ、この勤怠連絡してくれるBotをグループチャットに招待すると、動きません。
原因は、1対1の友達の状態でトーク相手のユーザー名を取得するAPIと、グループトークで投稿したユーザー名を取得するAPIが異なるためです。
全て記述すると長いので、部分的に以下のコードを追加するか書き換えてください。

action.js
// LINE情報取得
const replyToken = params.events[0].replyToken;
const userId = params.events[0].source.userId;
const groupId = params.events[0].source.groupId; // 追加:グループトークの場合はグループIDが取得できます。

// LINE ユーザー名取得(上は1対1のトーク用、下はグループトーク用)
//const getUserNameUrl = "https://api.line.me/v2/bot/profile/" + userId;
const getUserNameUrl = "https://api.line.me/v2/bot/group/" + groupId + "/member/" + userId;

グループトークの場合は、グループIDが取得できるので、それを用いてユーザー情報を取得しなければならない点が面倒なところです。三項演算子使えば良かったかな。。。

さいごに

いかがでしたでしょうか?LINEはLINE developersに登録しさえすれば、簡単に連携できますし、Slackも通知だけであれば、Incoming Webhookの設定さえしてしまえば、投稿は簡単に行えるので、連携させると意外に便利なものが出来るかもしれません。
今回の記事は、コード成分が多めでしたが、難しいコードはそんなに無いので、試しに作ってみようという方もチャレンジできる範囲かと思います。
また、IBM Cloud Functionsを利用した記事も書いてみたいと思いますが、第3回までお付き合いありがとうございました。
 第1回:IBM Cloud Functons 動かしてみた
 第2回:IBM Cloud FunctionsでNode.jsのパッケージを利用してみた
 第3回:IBM Cloud Functions でLINEとSlack連携させてみた

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?