GASとTrello APIで不要カードを一括削除する

普段の業務では、Trelloでタスク管理をしているのですが、不要になったカードを削除するのがとても手間だったので、自動で完全削除までしてくれるスクリプトを書いてみました。

# 使うもの
- Trello (Trello API含む)
- Google Apps Script (以降、GAS)

※前提：Trello, およびGoogleのアカウントは作成済みであること

# 作ったもの
Trelloにて、ゴミ箱用のボードを１つ作り、そのボードに含まれるカードをTrello APIで定期的に自動で削除するようにGASで実装しました。
スクリプトが実行されると、次のようにチケットが自動で削除されます。（ゴミ箱を空にするイメージ）
![empty_board.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/4ca7d586-161b-11f2-b00b-89be42c1340c.gif)

# 作ってみる
## ゴミ箱用のボードを用意する
ボードを新規作成するか、もしくは不要になった既存のボードを使っても問題ありません。
今回作成したスクリプトでは、ボード名で対象となるボードを識別するため、ゴミ箱にするボードの名前は、他のボードとは被らないような名前を付けてください。
以降、「ゴミ箱(完全削除用)」という名前でボードを作成したものとして進めます。

## Trello APIのAPIキーと認証トークンを発行する
Trello APIを実行するために、APIキーとトークンの発行を行います。

### APIキーの発行＆確認
まず、以下のURLにアクセスし、表示されたAPIキーをメモしておきます。
https://trello.com/app-key/
![APIキー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/e4458e5a-4180-1f67-051c-4c6fddea943d.png)
次に、説明中の「トークン」リンクをクリック、または以下のURLにAPIキーを含めたURLにアクセスします。
https://trello.com/1/authorize?expiration=never&scope=read,write,account&response_type=token&name=Server%20Token&key={APIキー}
![認証.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/b53be714-0963-c255-12a1-76f3c69584cd.png)
表示された画面の下部の「許可」ボタンを押すと、トークンが発行されるので、こちらもコピーしてメモしておきます。
![トークン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/b90280c3-e1d7-f1a5-a9c4-097b5d3183d0.png)

## GASの実装
ボード名から削除対象のボードを特定し、そのボード内のカードを全て削除し、リストは全てアーカイブするようにGASを実装します。


### 定数の定義
まず、各所で使用する定数を定義します。
APIキーと、トークンは先程メモしたものを記載してください。

```javascript
const TRELLO_API_HOST = "https://api.trello.com/1";
const DEV_API_KEY = "{APIキー}";
const API_TOKEN = "{トークン}";
const TARGET_BOARD_NAME = "ゴミ箱(完全削除用)";
```

### APIリクエスト用の関数
次に、APIリクエストを実行するための関数を用意します。
Trello APIのリクエスト上限に引っかからないように、リクエスト後に0.12秒待つように実装しています。
約83リクエスト/10秒なので、トークンごとの上限である100リクエスト/10秒には引っかからないようにしています。

※リクエスト上限について詳しくは以下のページをご参照ください。

https://help.trello.com/article/838-api-rate-limits

```javascript
// 指定したURLに対し、リクエストを実行する関数
function doRequest(url, method) {
  try {
    let response = UrlFetchApp.fetch(url, { method: method });
    // APIのリクエスト上限に引っかからないように少し待つ
    Utilities.sleep(120);
    return response;
  } catch (e) {
    console.log("エラー : line - " + e.lineNumber + "\n Error: " + e.message);
    return null;
  }
}
```

### Trello APIを実行する関数
続いて、使用するTrello APIを実行する関数を用意します。

```javascript
// 全てのボードを取得
// GET /1/members/me/boards
function getAllBoards() {
  let url = TRELLO_API_HOST + '/members/me/boards?key=' + DEV_API_KEY + '&token=' + API_TOKEN + '&fields=name';

  let result = doRequest(url, 'GET');
  if (result == null || result.getContentText().length == 0) {
    return null;
  }
  return JSON.parse(result.getContentText());
}

// ボード内のリストを全て取得
// GET /1/boards/{id}/lists
function getListsOnBoard(board) {
  let url = TRELLO_API_HOST + '/boards/' + board.id + '/lists?key=' + DEV_API_KEY + '&token=' + API_TOKEN;

  let result = doRequest(url, 'GET');
  if (result == null || result.getContentText().length == 0) {
    return null;
  }
  return JSON.parse(result.getContentText());
}

// 指定したリストをアーカイブ
// PUT /1/lists/{id}/closed
function closeList(list) {
  let url = TRELLO_API_HOST + '/lists/' + list.id + '/closed?key=' + DEV_API_KEY + '&token=' + API_TOKEN + '&value=true';

  let result = doRequest(url, 'PUT');
  if (result == null) {
    return null;
  }
  console.log('リストをアーカイブしました: ' + list.name);
}

// 指定したリスト内のカードを全て取得
// GET /1/lists/{id}/cards
function getCardsInList(list) {
  let url = TRELLO_API_HOST + '/lists/' + list.id + '/cards?key=' + DEV_API_KEY + '&token=' + API_TOKEN;

  let result = doRequest(url, 'GET');
  if (result == null || result.getContentText().length == 0) {
    return null;
  }
  return JSON.parse(result.getContentText());
}

// 指定したカードを削除
// DELETE /1/cards/{id}
function deleteCard(card) {
  let url = TRELLO_API_HOST + '/cards/' + card.id + '?key=' + DEV_API_KEY + '&token=' + API_TOKEN;

  let result = doRequest(url,'DELETE');
  if(result != null) {
    console.log('カードを削除しました: ' + card.name);
  } 
}
```

### ゴミ箱用ボードを空にする関数
最後に、Trello APIを実行する各関数を組み合わせて、ゴミ箱用ボードを空にする関数を実装します。

やることは以下の５つです。

1. 全てのボードを取得
2. ボード名から対象のボードを特定して抽出
3. ボードから全てのリストを取得
4. 取得したリストから、リスト内の全カードを取得し、1件ずつ削除
5. 空にしたリストはアーカイブ
※4〜5はリストの数だけ繰り返し実行

コードは以下の通り。

```javascript
// ボードを空にする
function emptyTargetBoard() {
  // 1. 全てのボードを取得
  let boards = getAllBoards();
  if (boards == null) {
    return;
  }
  // 2. ボード名から対象のボードを特定して抽出
  let targetBoard = boards.find((v) => v.name == TARGET_BOARD_NAME);
  console.log(targetBoard);
  if (targetBoard == null) {
    // 対象ボードが見つからない場合は何もしない
    return;
  }
  // 3. ボードから全てのリストを取得
  targeetLists = getListsOnBoard(targetBoard);
  console.log(targeetLists);
  if (targeetLists == null || targeetLists.length == 0){
    return;
  }
  // 4. 取得したリストから、リスト内の全カードを取得し、1件ずつ削除
  targeetLists.forEach(list => {
    targetCards = getCardsInList(list);
    targetCards.forEach(card => deleteCard(card));
    // 5. 空にしたリストはアーカイブ(削除機能がないため)
    closeList(list);
  });
}
```

### 実装したGASのソースコード
ソースコードの全文はGitHubにて公開しています。

https://github.com/raku2wei/empty-trello-board

## トリガーの設定

GASの実装が完了したら、自動で実行されるようにトリガーを設定します。

スクリプトエディタの左側のメニューから、「トリガー」を選択肢、トリガー設定を開きます。
![メニュー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/2dc8ff78-fad5-d297-d5d6-6149d9d3cabc.png)

次に、画面右下の「トリガーを追加」ボタンでトリガーを追加します。
![トリガー追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/9472e192-7753-e8b9-9498-75cf492310bd.png)

トリガーの設定画面が表示されたら、以下のように設定し、「保存」します。
※今回は、毎月1日の午前0〜1時の間にゴミ箱ボードを空にする という設定にしていますが、任意で変更してOKです。
![トリガー設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/13859d0c-4b13-22b2-c7c1-4997bbb7d59a.png)

## GASの実行ログ

実装したGASを実行すると、以下のようなログが出力されます。

![実行ログ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/1388d05c-586f-565c-2b7f-322b2555021e.png)
　　(省略)
![実行ログ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751276/542c8b66-68b5-6454-3d02-2b5bbf9a29bb.png)


# 参考にした記事

- Google Apps Script からTrello API を使ってTrelloを操作する

https://www.virment.com/use-trello-api-google-apps-script/

- Trello API 公式リファレンス

https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/

https://developer.atlassian.com/cloud/trello/rest/api-group-actions/

https://help.trello.com/article/838-api-rate-limits

# まとめ
手動でカード削除するのが手間だったので、今回はこのようなものを作ってみました。
Trello APIを使えば、手動で出来る操作は基本的に全て出来そうな感じなので、他にも自動化できる作業がないか色々試してみようと思います。
あと、今回はシンプルにログをコンソールに出力しているだけですが、少し改修すれば実行ログをSlackに投稿するというもの出来そうなので、やってみたい方は以下の記事を参考にしてみてください。

https://toranoana-lab.hatenablog.com/entry/2020/03/13/173949