1
2

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.

Slackの古い投稿を自動定期削除する

Posted at

やりたいこと

Slackの特定のチャンネルに投稿された発言を定期的に削除したい

仕様とか

  • 投稿から一定時間経過したSlackの発言を削除する
  • 一度に削除できる投稿数には上限がある (https://api.slack.com/lang/ja-jp/rate-limit)
  • サーバーレス (GASを利用)
  • パブリック・プライベートチャンネル両方対象
  • ユーザー権限で実行するためSlackAppのTokenを発行するユーザー自身に投稿削除権限が必要

手順

Slack側の設定

1) Appの作成

Slack API のサイトに行き、右上の Your apps からApp管理ページを開く
https://api.slack.com/

Create New App からAppの作成画面を開く

From scratch を選択

App Name は適当に
Pick a workspace to develop your app in: でAppを追加したいワークスペースを選択

2) 必要な権限の追加

Basic InformationAdd features and functionality のプルダウンの中に Permissions というのがあるので選択する

Scopes の項で User Token ScopesAdd an OAuth Scope を選択する
※Botではない方

プルダウンから必要な権限を選んで追加していく

  • channels:history
  • channels:read
  • chat:write
  • groups:history
  • groups:read

権限が追加されたことを確認

3) Appのインストール

権限が追加できたら左カラムのメニューから Basic Information を選択して基本情報画面に戻ります
Install your app のプルダウンを開き Install to Workspace を選択

確認画面にリダイレクトされるので 許可する を選択

Install your app の項目にチェックが付けばOK

4) Tokenの取得

Appのインストールが完了したら左カラムのメニューから OAuth & Permissions へ遷移する
OAuth Tokens for Your Workspace という項目に先ほど作成した User Token ScopesOAuth Token があるのでエディタなどにコピーして控えておく

Google Spreadsheet側の設定

1) 自動削除したいチャンネル一覧を作成する

Google Spreadsheetで新しいスプレッドシートを作成する
https://docs.google.com/spreadsheets/

自動削除をしたいチャンネルを記入する
シート名はなんでもOK

2) スクリプトの作成

メニューから ツール > スクリプト エディタ を選択

スクリプトに適当なファイル名をつける

以下のコードを貼り付ける
1行目の token にはSlackの設定で作成した User OAuth Token をセットする

cleaner.gs

const token = 'xoxp-23********177-23********317-23********648-e6bd********e7********a********c';
const hours = 8; // 何時間遡るか
const limit = 3; // 最大追加取得回数
const second = 0.8; // 何秒ごとに削除を実行するか

/**
 * main
 */
function main() {
  const values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  const channelNames = [];
  for (const value of values) {
    channelNames.push(value[0]);
  }
  for (const channelName of channelNames) {
    Logger.log("[%s]",channelName)
    cleanChannel(token, channelName);
  }
}

/**
 * チャンネル内投稿削除
 */
function cleanChannel(token, channelName) {
  Logger.log('*** cleanChannel start');

  const channelId = getChannelId(token, channelName);
  Logger.log("channelId: #" + channelName + ' | ' + channelId)
  if (channelId.length === 0) {
    return;
  }

  const date = new Date();
  const timestamp = Math.round(date.setHours(date.getHours()-hours) / 1000); // 設定した時間前
  Logger.log("timestamp: " + timestamp + ' | ' + new Date(timestamp * 1000).toString());

  let result;
  let count = 0;
  do {
    result = getChannelHistory(token, channelId, timestamp);
    count++;
    if (result.ok) {
      for (const message of result.messages.reverse()) {
        const deleteResult = deleteChat(token, channelId, message.ts);
        if (deleteResult.ok) {
          Logger.log("delete success");
        }
        Utilities.sleep(second * 1000); // error429回避のため一定時間sleep
      }
    }
  } while (result.ok && result.has_more && count < limit)
}

/**
 * チャンネル名 -> チャンネルID取得
 */
function getChannelId(token, channelName) {
  Logger.log('*** getChannelId start');

  const result = getChannelList(token);
  if (result.ok) {
    for (const channel of result.channels) {
      if (channel.name === channelName) {
        return channel.id;
      }
    }
  }
  return '';
}

/**
 * チャンネル一覧取得
 */
function getChannelList(token) {
  Logger.log('*** getChannelList start');

  const endpoint = 'https://slack.com/api/conversations.list';
  const options = {
    method: 'post',
    payload: {
      token: token,
      types: 'public_channel,private_channel'
    }
  };
  const response = UrlFetchApp.fetch(endpoint, options);
  Logger.log(JSON.parse(response));
  return JSON.parse(response);
}

/**
 * チャンネル内投稿取得
 */
function getChannelHistory(token, channelId, timestamp) {
  Logger.log('*** getChannelHistory start');

  const endpoint = 'https://slack.com/api/conversations.history';
  const options = {
    method: 'post',
    payload: {
      token: token,
      channel: channelId,
      latest: timestamp + '.00000',
      inclusive: true
    }
  };
  const response = UrlFetchApp.fetch(endpoint, options);
  Logger.log(JSON.parse(response));
  return JSON.parse(response);
}

/**
 * 投稿削除
 */
function deleteChat(token, channelId, timestamp) {
  Logger.log('*** deleteChat target: ' + timestamp);
  const endpoint = 'https://slack.com/api/chat.delete';
  const options = {
    method: 'post',
    payload: {
      token: token,
      channel: channelId,
      ts: timestamp
    }
  };
  let response = UrlFetchApp.fetch(endpoint, options);
  Logger.log(JSON.parse(response));

  // error429が出た場合は指定秒数sleepさせる
  while (!response.ok && response.status == 429) {
    const retryAfter = response.headers['retry-after'];
    Utilities.sleep(retryAfter * 1000);
    response = UrlFetchApp.fetch(endpoint, options);
    Logger.log(JSON.parse(response));
  }
  return JSON.parse(response);
}

3) トリガーの設定

左のメニューから トリガー を選択する

画面右下の + トリガーを追加 ボタンを押す

定期実行の設定を行う

参考にした情報

基本的なロジックは以下を参考にさせていただき、現在のAPI仕様に対応していなかったり一部動作しない箇所を修正しました。
https://qiita.com/hideo-works/items/21c2a6784b7f6caa4c06
https://tech.naturalmindo.com/notwork_slackapi/

以上

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?