自分を活かして 相手を活かして 今を活かす

【GAS】Slackのプライベートチャンネルのリストを取得し、複数チャンネルにメッセージを送信する方法について

こんにちは、おかちゃんせんせいです!

現在、仕事上での連絡のやり取りにSlackを使っています。
他の人も同じようにSlackを使っているので、何か通知をするときにはメールよりもSlackへ通知をしてほしいという要望が。

そこで、この機会にGASを用いてSlackへ通知をする仕組みを作ることにしました。

前提条件
  • 複数のプライベートチャンネルへのメッセージ送信
  • チャンネル数は現在500近くあり、流動的に変化する
  • チャンネルIDは特に控えていない

◆お知らせ◆

【まとめ記事】

現在、これまで書いてきた記事をテーマ別にまとめています。

詳しくはこちらから

Slackの複数プライベートチャンネルに一斉送信するためには?

要望を実現する方法についてまずは調べてみる

Slack APIは使ったことがなかったので、まずはググってみることに。

すると、大抵出てくるのはパブリックチャンネルへの投稿方法だったり、Botを使った投稿方法ばかりで、プライベートチャンネルへの投稿はほとんどなくて。

あったとしても、GASではなくて、別の開発言語のものなので参考にはできません。

また、チャンネル一覧を取得するというのも検索してもパッと見つからず、あれこれ検索ワードを変えてようやくすぐに使えそうな記事を見つけました。

しかし、それでも検索結果上位で紹介されているGASのコードについて、2023年5月現在では使えなくなっていたのも多かったので、自分が躓いた点も踏まえて共有いたします。

まず、今回実現したいことを整理すると、大きく3つに分けることができます。

  1. Slack APPの作成および特定の権限の付与
  2. プライベートチャンネルの一覧を取得し、チャンネルIDをGETする
  3. GETしたチャンネルIDに対して、それぞれ異なる内容のメッセージを送信する

①はSlack側、②・③はGAS側の設定になります。

今回実現したいのはプライベートチャンネル(テスト1〜3)にメッセージを自動送信する

Slack側の設定〜①Slack APPの作成および特定の権限の付与〜

まず、最初のプライベートチャンネルの一覧を取得するコードを書く前に、Slackの機能を使うためにSlack APPの作成や情報を取得・メッセージを送信するための権限を付与する必要があります。

Slack Apiのページを開き、『Create an App』をクリックする

一度作成したことがある場合には、下記の画面から『Create an App』をクリックします。

『From scratch』をクリックする
Appの作成

App NameAppの名前(任意の名前)、Pick a workspace to develop your app in: にはプライベートチャンネルのあるワークスペースを選択し、『Create App』をクリックするとAppが作成されます。

Topページに戻り、「OAuth & Permissions」を選択する
User Token Scopesの枠内ある『App an OAuth Scope』を選択し、必要な権限を付与する

今回必要な権限は下記3つ。

・chat:write
・groups:read
・channels:read

ただし、channels:readはパブリックチャンネルの情報を取得する時に必要なだけなので、プライベートチャンネルだけが対象の場合には不要となります。

トークンを発行するために『Install to Workspace』をクリックする
下画面が出たら『許可する』をクリックする
トークンをCopyする

こちらでCopyしたトークンは後ほどスクリプトプロパティに設定します。

GAS側の設定

Slack APIを利用する準備が整いましたら、次はコードを書いていきます。

チャンネル一覧を出力するスプレッドシートからApps Scipt([拡張機能] – [Apps Script])を作成します。

まずは、スクリプトプロパティに先ほど発行したトークンをコピーします。
[プロジェクトの設定] – [スクリプト プロパティを編集]で、プロパティと値を入力したら『保存』をクリックして完了です。

こちらのコードをコピー&ペーストします。

GAS
//----------------------------------
// プライベートチャンネルのリストを取得する
//---------------------------------- 
function sendChannel(){
  try{
    getPrivateChannelList();
    console.log('Slackへの一斉送信が完了しました。')
  }catch(e){
    console.error(e.message);
  }
}


//----------------------------------
// プライベートチャンネルのリストを取得する
// 参考:https://qiita.com/tkazu0806/items/4c6a27736e4bc7329e9d
//---------------------------------- 
function getPrivateChannelList() {
  // スクリプトプロパティに設定した値を取得する
  const token = PropertiesService.getScriptProperties().getProperty("slackApiToken");
  let options = { "limit" : 200, "types" : "private_channel" };
  let channels = slackApi(token, "conversations.list", options);

  // プライベートチャンネルリストを書き込みスプレッドシートを設定する
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName('PrivateList');
  let lastRow = sheet.getLastRow();
  if(lastRow < 2){
    lastRow = 2;
  }

  // データ列を一旦全てクリアして、ヘッダー項目を設定する
  sheet.getRange(1,1,lastRow-1,1).clearContent();
  sheet.getRange("A1").setValue("チャンネル名");
  sheet.getRange("B1").setValue("チャンネルID");

  let targetCellNumber = 2;
  while(true) {
    channels.channels.forEach( function (ch) {
        sheet.getRange("A"+targetCellNumber).setValue(ch.name);
        sheet.getRange("B"+targetCellNumber).setValue(ch.id);
        targetCellNumber = targetCellNumber + 1;
    });

    // 結果の続きがない場合にはbreak
    if(channels.response_metadata.next_cursor == "") break;
    // 次のページのoptionsをセットして、再度APIを呼び出す
    options = { "limit" : 200, "types" : "private_channel", "cursor" : channels.response_metadata.next_cursor };
    channels = slackApi(token, "conversations.list", options);
  }

  //シート内でソートしたいセル範囲をgetRangeで指定する
  lastRow = sheet.getLastRow();
  let data = sheet.getRange(2, 1, lastRow-1, 2);
  //列Eを基準に降順でソートする
  data.sort({column: 1, ascending: true});
  // 出力するデータを配列に格納する
  let sendData = sheet.getRange(2,1,lastRow-1,2).getValues();
  // Slackのチャンネルにメッセージを一斉送信する
  sendToSlackFromSP(sendData,token);
}


//------------------------
// SlackAPIで情報を取得する
//------------------------ 
function slackApi(token, path, options) {
  if (!options) options = {};
  // 引数のpathをコネクトする
  var url = "https://slack.com/api/" + path + "?";
  // 引数のoptionsをエンコードして、1要素ずつ配列にpush
  var qparams = [];
  for (var key in options) {
    qparams.push(encodeURIComponent(key) + "=" + encodeURIComponent(options[key]));
  }
  // urlにqparmsを1要素ずつ「&」でコネクトする
  url += qparams.join('&');
  // HTTPヘッダーの認証情報である「Bearer」 + トークン情報をつける
  var headers = {
    'Authorization': 'Bearer ' + token
  };
  // 上で作成されたアクセストークンを含むヘッダ情報を連想配列する
  var setoptions = {
    'headers': headers
  };

  // urlとヘッダー情報を基にして、WebAPIで情報を取得する
  var res = UrlFetchApp.fetch(url,setoptions);
  var ret = JSON.parse(res.getContentText());
  if (ret.error) {
    throw "GET " + path + ": " + ret.error;
  }
  return ret;
}


//------------------------------------------------
// SlackAPIでメッセージ送信するために必要な情報を設定する
//------------------------------------------------ 
function setChannelToSlack(message_data,token,channel_id){
  // HTTPヘッダーの認証情報である「Bearer」 + トークン情報をつける
  var headers = {
    "Authorization": "Bearer " + token
  };
  // API固有情報を設定する
  var payload = {
    'channel' : channel_id,
    'text' : message_data,
  }
  // 各種情報をoptionsに格納して、returnする
  var options = {
    'method' : 'post',
    'headers' : headers,
    'contentType': "application/x-www-form-urlencoded",
    'payload' : payload
  };

  return options;
}


//-------------------------------------
// Slackの各チャンネルにメッセージを送信する
//-------------------------------------
function sendToSlackFromSP(sendData,token){
  sendData.forEach((key) => {
      // メッセージの内容を設定する
      var message_data = key[0] + '様宛のメッセージ';
      // setChannelToSlack関数を呼び出してAPIに必要な情報を整理
      var created_message = setChannelToSlack(message_data,token,key[1]); 
      // メッセージを投稿
      var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", created_message); 
    }
  )
}

上記コードを書いて実行すれば、各プライベートチャンネルにメッセージが送信されます。

今回の躓きポイント:API連携部分の仕様変更

今回躓いたポイントは、
API連携部分
です。

過去の記事(2021年頃まで)を拝見する限りでは、トークンをUrlFetchApp.fetchで受け渡せば情報が取得できたようです。


しかし、2023年5月現在ではできないので、その頃の記事を参考にするだけではinvalid_authでエラーになってしまう可能性があります。

それでいろいろ調べたところ、発行したトークンを設定するだけではなく、それ以外にheader情報を渡す必要があることがわかりました(下記の部分)。

GAS
  // HTTPヘッダーの認証情報である「Bearer」 + トークン情報をつける
  var headers = {
    'Authorization': 'Bearer ' + token
  };
  // 上で作成されたアクセストークンを含むヘッダ情報を連想配列する
  var setoptions = {
    'headers': headers
  };

あとは、パラメータとしてlimitとtypeをurlに設定すれば、Web APIで情報を取得できるようになります。

GAS
//------------------------
// SlackAPIで情報を取得する
//------------------------ 
function slackApi(token, path, options) {
  if (!options) options = {};
  // 引数のpathをコネクトする
  var url = "https://slack.com/api/" + path + "?";
  // 引数のoptionsをエンコードして、1要素ずつ配列にpush
  var qparams = [];
  for (var key in options) {
    qparams.push(encodeURIComponent(key) + "=" + encodeURIComponent(options[key]));
  }
  // urlにqparmsを1要素ずつ「&」でコネクトする
  url += qparams.join('&');
  // HTTPヘッダーの認証情報である「Bearer」 + トークン情報をつける
  var headers = {
    'Authorization': 'Bearer ' + token
  };
  // 上で作成されたアクセストークンを含むヘッダ情報を連想配列する
  var setoptions = {
    'headers': headers
  };

  // urlとヘッダー情報を基にして、WebAPIで情報を取得する
  var res = UrlFetchApp.fetch(url,setoptions);
  var ret = JSON.parse(res.getContentText());
  if (ret.error) {
    throw "GET " + path + ": " + ret.error;
  }
  return ret;
}

まとめ

もしSlack APIのことをネット検索して調べる場合には、2022年以降に書かれた記事を確認することを推奨いたします。

とりあえずやりたいことが実現できたので、

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA