やりたいこと
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/
App Name は適当に
Pick a workspace to develop your app in:
でAppを追加したいワークスペースを選択
2) 必要な権限の追加
Basic Information
の Add features and functionality
のプルダウンの中に Permissions
というのがあるので選択する
Scopes
の項で User Token Scopes
の Add 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 Scopes
の OAuth Token
があるのでエディタなどにコピーして控えておく
Google Spreadsheet側の設定
1) 自動削除したいチャンネル一覧を作成する
Google Spreadsheetで新しいスプレッドシートを作成する
https://docs.google.com/spreadsheets/
自動削除をしたいチャンネルを記入する
シート名はなんでもOK
2) スクリプトの作成
以下のコードを貼り付ける
1行目の token
にはSlackの設定で作成した User OAuth Token
をセットする
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/
以上