のむログ

技術メモ / 車 / 音楽 / 雑記 / etc...

Slack App + GAS で【学生団体】の作業効率を爆上げ【その1】

今年イチ!お勧めしたいテクニック by ゆめみ feat.やめ太郎 Advent Calendar 2019 8日目

ふとみたら、投稿されてない日程があったので、投稿しました😇 最近Google App Scriptsを使ったサーバーレスのシステムが多いので、作ってみました。

ゆめみさんのアドカレは、実は去年も書いていてAmazonギフトをいただいているので、何かとご縁がありそうです。その時の記事リンクを貼っておきます。ぜひ、見てください🙇‍♂️

RaspberryPi + OBD2 で取得した車両情報を解析してみる(オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018 7日目)

RaspberryPi 3 Model B+ でlircを使ってリモコン化する(その1)(オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018 8日目)

RaspberryPi 3 Model B+ でlircを使ってリモコン化する(その2)(オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018 9日目)

2019/12/13 追記

続編

www.nomunomu0504.work

TL;DR

問題は身近なところから(前置き)

所属しているでは学生団体( 学生団体with: http://with-sabae.com/ )は、連絡手段としてLINE以外にも、Slackを導入してます。

毎週1回会議を開いていて、各会議に関するブログ( https://ameblo.jp/gakuren/ )を書いているんですが、ブログを投稿すると、SNS連携しているTwitterには自動でブログを書いたことを投稿してくれますが、Facebookに関しては思うようにできません。

Facebookに投稿はできるのですが、学生団体のFacebookページ( https://www.facebook.com/sabaewith/ )に投稿したいのです。ブログのSNS連携では個人のFB投稿として、扱われてしまうため無意味だったという...🤔

さらに条件として、Facebookページへ誰でも投稿できると、ちょっとマズいので権限付与済の人しか投稿できないようにしてます。そのため、権限のない人が投稿しようとすると、権限のある人に頼まないといけなくなります(とても面倒)

じゃあSlackからできればいいじゃん

となると、Slackからブログ投稿に関することだけ投稿できるようになればいいと思い立ったわけです。Slackから「ブログURL」「Facebookに投稿する内容」を記入すると、Facebookページに投稿してくれるという仕組みを作りました。

処理フロー

flow.001.png

①:入力するためのダイアログ取得コマンドを実行
②:入力ダイアログの項目を取得して表示
③:ダイアログに入力された内容を送信
④:取得した内容を元にFacebookページに投稿
⑤:投稿結果の取得
⑥:投稿結果をSlackチャンネルに投稿

参考にしたサイト/記事一覧(とても参考になりました)

qiita.com

qiita.com

qiita.com

www.indetail.co.jp

開発準備

以下のサイトがとても分かりやすいので、僕の方では割愛します。

qiita.com

qiita.com

事前準備として様々な設定をしておきます。 まずは、Google App Scriptのファイルを作成して、「公開」→「Webアプリケーションとして導入」で、scriptのアドレスを取得して、メモしておきます(↓こんなやつ) image.png

作成したScriptファイルに全ての処理を記載して行くことになります。

次に、SlackAppを作成して「Incoming Webhooks」「Incoming Components」「Bot User」を設定してください。 「Incoming Webhooks」設定時のURLは、ScriptファイルのURL、channelには処理の実行結果を出力したいチャンネルを選択してください。

「OAuth & Permissions」は次の設定をしておきます。
「bot」「chat:write:bot」「chat:write:user」「commands」「incoming-webhook」

ブログ投稿用の入力ダイアログを表示するために、自作のSlash commandを作成します。 Slackでは「Slash Command」を自作することができるAppが公開されています。ここから、Slash Commandを設定してください。

image.png

「Command」に入力した文字列(ここでは"/blog_to_facebook")は、後からスクリプトを書くときに使うので、メモしましょう。「Request URL」には、さっき取得したScriptファイルのアドレスを記入してください。

開発

全てのコードを先に記載しておきます。下記に個別で解説を入れていきます。

システムコード全体

/**
 * Slackからのリクエストを受ける処理
 */
function doPost(e) {
  
  // SlackApp VerificationToken
  var slackAppToken = '[CHANGE_YOUR_SLACK_APP_TOKEN]';
  // Slack OAuthKey
  var OAuthKey = '[CHANGE_YOUR_SLACK_OAUTH_KEY(ex. xoxp-...)]';
  
  if (e.parameter.command == "/blog_to_facebook")
  {
    if (e.parameter.token != slackAppToken)
    {
      throw new Error("blog_to_facebook token error");
    }
    var trigger_id = e.parameter.trigger_id;
    var message_ts = e.parameter.message_ts;
    var slackUrl = "https://slack.com/api/dialog.open";
    
    var headers = {
      "Authorization": "Bearer " + OAuthKey
    };
    
    var payload = {
      "token": OAuthKey,
      "trigger_id": trigger_id,
      "dialog": JSON.stringify({
        "callback_id": "blog_to_facebook",
        "notify_on_cancel": true,
        "state": "Limo",
        "title": "Facebookに投稿する",
        "submit_label": "投稿する",
        "elements": [
          {
            "type": "text",
            "label": "ブログのURLを入力",
            "name": "blog_url"
          },
          {
            "type": "textarea",
            "label": "Facebookに投稿する内容を入力",
            "name": "facebook_content"
          }
        ]
      })
    }
    
    var options = {
      'method' : 'post',
      "headers": headers,
      'payload': payload
    }; 
    UrlFetchApp.fetch(slackUrl, options);
    return ContentService.createTextOutput();
  }
  else
  {
    var json = JSON.parse(decodeURIComponent(e.parameter.payload));
    if (json.token != slackAppToken)
    {
      throw new Error("incoming web hooks token error");
    }
    
    var SlackChannelPostUrl = 'https://hooks.slack.com/services/xxxxxx/xxxxxxx/XXxxXXXXXxXXxXXxx';
    
    //>>>>>> Before Posting to Facebook
    var payload = {
      "attachments": [{
            "color": "#36a64f",
            "pretext": "以下の内容でFacebookに投稿します。",
            "fields": [
              {
                "title": "blogのURL",
                "value": json.submission.blog_url,
                "short": false
              },
              {
                "title": "Facebook投稿内容",
                "value": json.submission.facebook_content,
                "short": false
              }
            ]
        }
      ]
    };  

    var options = {
      "method" : "post",
      "contentType" : "application/json",
      "payload" : JSON.stringify(payload)
    };

    UrlFetchApp.fetch(SlackChannelPostUrl, options);
    //<<<<<< Before Posting to Facebook
    
    //>>>>>> Post Facebook
    var BaseUrl = "https://graph.facebook.com/v5.0/";
    var PageId = "ooooooooooooooo";
    var AppID = "xxxxxxxxxxxxx";
    var AppSecret = "zzzzzzzzzzzzzzzzz";
    var PageAccessToken = "[CHANGE_YOUR_ACCESS_TOKEN]";
    var PostContentUrl = BaseUrl + PageId + "/feed?access_token=" + PageAccessToken;
    
    var PostContentBody = "【ブログを更新しました!】\n\n";
    PostContentBody += json.submission.facebook_content + "\n\n";
    PostContentBody += json.submission.blog_url;
    
    var PostContents = {
      "method" : "post",
      "contentType" : "application/json",
      "payload": JSON.stringify({
        "message": PostContentBody,
        "link": json.submission.blog_url
      })
    };
    
    //<<<<<< Posting
    var PostingComment = {
      "attachments": [{
            "color": "#36a64f",
            "fields": [
              {
                "title": "Facebookへ投稿中...",
                "value": "",
                "short": false
              },
            ]
        }
      ]
    };  

    var options = {
      "method" : "post",
      "contentType" : "application/json",
      "payload" : JSON.stringify(PostingComment)
    };

    var response = UrlFetchApp.fetch(SlackChannelPostUrl, options);
    //>>>>>> Posting
    
    var postResText = "";
    var postResColor = "";
    try {
      UrlFetchApp.fetch(PostContentUrl, PostContents);
      postRes = "成功しました。";
      postResColor = "#36a64f";
    } catch (e) {
      postRes = "失敗しました( " + e + " )";
      postResColor = "#f24646";
    }
    //<<<<<< Post Facebook
    
    var payload = {
      "attachments": [{
            "color": postResColor,
            "fields": [
              {
                "title": "投稿結果",
                "value": postRes,
                "short": false
              },
            ]
        }
      ]
    };  

    var options = {
      "method" : "post",
      "contentType" : "application/json",
      "payload" : JSON.stringify(payload)
    };

    response = UrlFetchApp.fetch(SlackChannelPostUrl, options);
    return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
  }
}

①:入力ダイアログを生成するための処理を作成

Slack Appからデータを受信する

function doPost(e)
{
  // 省略
}

取得したスクリプトのアドレスにPostメソッドでアクセスされると、doPostが呼び出されるようになります。受け取ったデータは、引数の e に格納されています。

送信元をトークンを用いて認証/実行種類の特定

if (e.parameter.command == "/blog_to_facebook") // こっちの処理はダイアログ表示用
{
  if (e.parameter.token != slackAppToken)
  {
    throw new Error("blog_to_facebook token error");
  }
  // 中略
}
else  // こっちの処理はダイアログから受け取る用
{
  // 中略
}

Slash Commandが実行されたことにより、スクリプトがコールされたことを e.parameter.command に格納されている文字列を使って確かめています。

表示するダイアログデータの設定

var payload = {
  "token": OAuthKey,
  "trigger_id": trigger_id,
  "dialog": JSON.stringify({
    "callback_id": "blog_to_facebook",
    "notify_on_cancel": true,
    "state": "Limo",
    "title": "Facebookに投稿する",
    "submit_label": "投稿する",
    "elements": [
      {
        "type": "text",
        "label": "ブログのURLを入力",
        "name": "blog_url"
      },
      {
        "type": "textarea",
        "label": "Facebookに投稿する内容を入力",
        "name": "facebook_content"
      }
    ]
  })
}

このコードにより、作成されるダイアログはこんな感じ スクリーンショット 2019-12-09 23.04.18.png

elements内にダイアログに表示したいパーツを記載していきます。Slackの公式サイトにさまざまな種類のフォームが載ってます。 api.slack.com

あと、どんな感じで表示されるか試すことのできる、公式サイトあるので、リンク貼っておきます。 api.slack.com

②:入力ダイアログの項目を取得して表示

チャンネルへダイアログデータを送信

var headers = {
  "Authorization": "Bearer " + OAuthKey
};

var options = {
  'method' : 'post',
  "headers": headers,
  'payload': payload
}; 

UrlFetchApp.fetch(slackUrl, options);
return ContentService.createTextOutput();

作成したダイアログデータをJSON形式に変換してからチャンネルへ送信します。最後に必ず ContentService.createTextOutput(); を実行してください。正しくダイアログが表示されない場合があります。

③:ダイアログに入力された内容をGASに送信

さて、①, ②でダイアログを表示することはできるようになりました。次はダイアログに入力されたデータを取得して、GAS上で処理できるようにします。①の部分に記載してあるように、ダイアログから受け取る方を作っていきます。

var json = JSON.parse(decodeURIComponent(e.parameter.payload));
if (json.token != slackAppToken)
{
  throw new Error("incoming web hooks token error");
}

ダイアログから送信されたデータは e.parameter.payload に格納されています。が、中身はJSON形式の文字列で書かれているため、JSON.parseを使ってオブジェクト化します。その後、Tokenによるチェックを行ったあと、確認のために入力された内容をSlackのチャンネルに投稿します。

//>>>>>> Before Posting to Facebook
var SlackChannelPostUrl = 'https://hooks.slack.com/services/xxxxxx/xxxxxxx/XXxxXXXXXxXXxXXxx';
var payload = {
  "attachments": [{
    "color": "#36a64f",
    "pretext": "以下の内容でFacebookに投稿します。",
    "fields": [
      {
        "title": "blogのURL",
        "value": json.submission.blog_url,
        "short": false
      },
      {
        "title": "Facebook投稿内容",
        "value": json.submission.facebook_content,
        "short": false
      }
    ]
  }]
};  

var options = {
  "method" : "post",
  "contentType" : "application/json",
  "payload" : JSON.stringify(payload)
};

UrlFetchApp.fetch(SlackChannelPostUrl, options);
//<<<<<< Before Posting to Facebook

こんな感じでSlackチャンネルには表示されます。

スクリーンショット 2019-12-10 0.29.38.png

次回につづく

次は実際にFacebookページに投稿するための準備をするんですが、これがまた曲者で結構時間かかりました ^^;

www.nomunomu0504.work

学生団体withのご紹介

【学生団体with】

・ホームページ with-sabae.com

・Twitter twitter.com

・Facebook www.facebook.com

・Instagram instagram.com