[Slack × GAS] conversations.rename チャンネル名称を変更する

なんやかんやで名称を一気に変更したい時、ありますよね。
力isパワーでポチポチ手入力で変更しても良いのですが、件数が多くてだるすぎるのでGASでどうにかしたいと考えて試行錯誤しました。

その俺メモ。

まとめると、チャンネルIDを取得して、名称変更したいIDに対してuserOAuthTokenでRenameを効かせる、ってことになる。
ちゃんとリファレンスを読むと、この辺も実は書いてあったんだな〜。ちゃんと読むの大事っすね、はい。

コード

こんな構成。joinとLeaveは結局要らんかった。いつか使うかもしれない、勉強にはなった。

global

// スプレッドシート取得.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("チャンネルIDの取得");
const targetSheet= ss.getSheetByName("名称変更対象");

// SlackAPIで登録したボットのトークンを設定する.
//直接指定の場合は = "xoxb-から始まる上記Slack側の設定で取得したトークン";
const botUserOAuthToken = PropertiesService.getScriptProperties().getProperty('BotUserOAuthToken');
const userOAuthToken = PropertiesService.getScriptProperties().getProperty('UserOAuthToken');

トークンはプロパティに入れてます。


channel_List

参照資料から改変した主なポイント

  • 再代入しないものはconst宣言。

  • 値を書き込む時にsetValueを都度行っていると処理時間が掛かるので、配列に格納して一気に書き込む。

  • 削除時にsetValueを都度行っていると処理時間が掛かるので、clearメソッドを使う。と思ったけどシートは新規作成としたけど、この辺はお好みだな。

// 参照した資料
// https://shiimanblog.com/engineering/public-channels/#toc9


/**
 * Main Execution method.
 * 実行メソッド.
 */
function setChannelInfo() {
  public_channels = getChannelList("public_channel");
  const public_channels_array = public_channels.map(channel => [channel.id, channel.name, channel.topic.value, channel.purpose.value]);
  const headers = ["channel.id", "channel.name", "channel.topic.value", "channel.purpose.value"];
  public_channels_array.unshift(headers)
  const today = Utilities.formatDate(new Date(), 'JST', 'YYYYMMdd_HH:mm:ss');
  const newSheet = ss.insertSheet();
  newSheet.setName("channelList_"+today);
  newSheet.getRange(1, 1, public_channels_array.length, public_channels_array[0].length).setValues(public_channels_array);
}


/**
 * Function to get channel list via API.
 * チャンネルリスト ページャー対応.
 * 
 * @param {string} types - Optional arguments of Slack API. 
 * @return {object} channelList - Slack channel list
 * 
 * NOTE: Need to specify cursor to get more than 1000 pieces of information.
 * NOTE: https://api.slack.com/methods/conversations.list
 */
function getChannelList(types) {
  const maxPager = 10;   // ページャーの最大数.
  let count = 1;
  let channelList = [];
  let cursor = "";
  while (count <= maxPager) {
    channelObj = getChannelObject(types, cursor);
    channelList = channelList.concat(channelObj.channels);
    if (channelObj.response_metadata.next_cursor == "") {
      break;
    }
    cursor = channelObj.response_metadata.next_cursor;
    // console.log(cursor);
    // console.log(typeof cursor);
    count++;
  }

  return channelList;
}


/**
 * Function to get channel list via API.
 * APIでチャンネルリストを取得する。
 * 
 * @param {string} types - Optional arguments of Slack API. 
 * @param {string} cursor - Optional arguments of Slack API. 
 * @return {object} obj - Slack channel list
 * 
 * NOTE: Need to specify cursor to get more than 1000 pieces of information.
 * NOTE: https://api.slack.com/methods/conversations.list
 */
function getChannelObject(types, cursor) {
  const options = {
    "method": "get",
    "contentType": "application/x-www-form-urlencoded",
    "payload": {
      "token": botUserOAuthToken,
      "limit": 1000,
      "exclude_archived": true,
      "types": types,
      "cursor": cursor
    }
  }
  const url = 'https://slack.com/api/conversations.list';
  const response = UrlFetchApp.fetch(url, options);
  const obj = JSON.parse(response);
  return obj;
}

channel_Rename

function setRenameChannels() {
  const lastRow = targetSheet.getLastRow();
  const values = targetSheet.getRange(1, 1, lastRow, 2).getValues();

  values.forEach(value => renameChannel(value[0], value[1]));
}


function renameChannel(channelID, channelName) {
  const options = {
    "method": "post",
    "contentType": "application/x-www-form-urlencoded",
    "payload": {
      "token": userOAuthToken,
      "channel": channelID,
      "name": channelName
    }
  }
  const url = 'https://slack.com/api/conversations.rename';
  UrlFetchApp.fetch(url, options);
}


前提環境

ワークスペースの管理者またはオーナー権限を持っていること。
Slack Appを作成し、APIに必要なScopesを付与していること。

やりたいこと

n件の対象チャンネルのチャンネル名称を一気に変更したい

考えていた手順

  1. 名称変更したいチャンネルのIDを取得する

  2. 名称変更したいチャンネルにBotを参加させる →名称変更は管理者権限で行うからいらんかった

  3. 名称変更を行う 


使用するAPI

conversations.list
https://api.slack.com/methods/conversations.list

conversations.join →要らんかった
https://api.slack.com/methods/conversations.join

conversations.rename
https://api.slack.com/methods/conversations.rename

ハマったポイント

主に下記のポイントでぐぬぬとなっていた。特にcursor指定はちょっとまだ腹持ちしてないというか、理解が浅い。

  1. チャンネルIDを取得する際のcursor指定(ページャ、pagination)

  2. mapかforEachか引数二つの関数を呼び出すとき

  3. renameのエラー "error": "not_authorized" からのuserOAuthToken


1.チャンネルIDを取得する際のcursor指定(ページャ、pagination)

参考にしたブログ
https://shiimanblog.com/engineering/public-channels/

limit1000で1000件までしか取得してないけど、チャンネル1000件以上あるとcursorを工夫する必要がある。
1000件までしか取得できないからそれ以上取得するときは、cursorで指定する。
cursorは一回目取得したときのオブジェクトの中にかいてある。

“response_metadata”: {
“next_cursor”: ” XXXXXX=”
}

https://api.slack.com/docs/pagination

2.mapかforEachか、引数二つの関数を呼び出すとき

チャンネルIDをチャンネル名称を二つの引数としたいときに、あーどうしよってちょっと悩んだ。配列で解決した。オブジェクトで解決する方法もある。それは後述する。


3.renameのエラー "error": "not_authorized" からのuserOAuthToken


最初はID取得と同じようにBotトークンで実行していた。
GAS上では特にエラーも出ずに実行完了していたが、名称が変わらず、あれー?となっていた。
で、こういう場合は、まずTesterで再チェックと思い、テストしてみたところ、
https://api.slack.com/methods/conversations.rename/test

だった。
APIの説明をあらためて読んでいくと

管理権限を持ったユーザでないとRenameできない、と。
ここで、BotトークンからUserトークンに切り替え、成功となった。


やってみた


1.名称変更したいチャンネルのIDを取得する

https://shiimanblog.com/engineering/public-channels/ を参考の上、改変したコードを用いた。

アナリティクスからチャンネルIDもDLできれば良かったのだが、
チャンネルIDはDLできないのでGASで取得する。
上のプランだとDLできるのか?

参考 Slack アナリティクスダッシュボードを閲覧する

APIは conversations.list を利用し、チャンネルIDを取得する。
conversations.info はチャンネルIDがRequired arguments なので、今回のようにそもそもIDを取得したいねんという時には不向き。

また、ハマったポイントにも書いたように、このconversations.list を利用してチャンネルIDを取得するときに、1000件以上のチャンネル数が存在しているとpaginationに気を付ける必要がある。

自分の雑な理解だと、ワークスペースの情報を持っていて、そこに仮想的にページが割り付けられており、工夫しないとその最初のページの情報しか取ってこれない事になる。その工夫がcursorの指定になる。


2.名称変更したいチャンネルにBotを参加させる→名称変更は管理者権限で行うからいらんかった

最初はbotに各チャンネルに参加させて、botにRenameしてもらおうと考えていた。で、先にこのjoinのコード書いたんだけど、結局これはいらんのよね。供養のためにここにUPしてみる。Botはちゃんと各チャンネルに参加できました。これをconversations.leaveに書き換えれば、チャンネルから去っていきます。

function setJoinChannels() {
  const lastRow = targetSheet.getLastRow();
  const values = targetSheet.getRange(1, 1, lastRow, 1).getValues();
  const channelIDs = values.flat();

  channelIDs.forEach(channelID => joinChannel(channelID));
}

function joinChannel(channelID) {
  const options = {
    "method": "post",
    "contentType": "application/x-www-form-urlencoded",
    "payload": {
      "token": botUserOAuthToken,
      "channel": channelID
    }
  }
  const url = 'https://slack.com/api/conversations.join';
  UrlFetchApp.fetch(url, options);
}

3.名称変更を行う

スプレッドシートの「名称変更対象」シートはこんな感じで1列目にID、2列目に変更後の名称としています。

values.forEach(value => renameChannel(value[0], value[1]));

上記のように配列で引数に渡す形にしたが、
別解として、こんな提案も。
オブジェクトでこうするのかあ、なるほど。

function setRenameChannels() {
  const lastRow = targetSheet.getLastRow();
  const values = targetSheet.getRange(1, 1, lastRow, 2).getValues();
  const channelInfos = values.map(record => {
    const channelInfo = { id: record[0], name: record[1] };
    return channelInfo;
  });
  channelInfos.forEach(channelInfo => renameChannel(channelInfo.id, channelInfo.name));
}

スプレッドシートの値をオブジェクト化するときはこちらの情報が役立ちます。

なんやかんやオブジェクト使うからなあ。


ひとまず、今日はこんなとこで(雑)!


#GAS
#Slack
#API

いただいたサポートで、書籍代や勉強費用にしたり、美味しいもの食べたりします!