GAS × Slack Bot で全社ウォーキング大会 750 名のチームチャンネルを自動作成して参加者・運用者体験が向上した話（実装サンプル付き）

本記事は[株式会社 Works Human Intelligence](https://www.works-hi.co.jp/) の [アドベントカレンダー](https://qiita.com/advent-calendar/2023/advent-works-hi) の 22 日目の記事となります。

昨日は [@kbkb_k](https://qiita.com/kbkb_k) さんの記事「[【React】初学者向け React フック(hook)について知ろう](https://qiita.com/kbkb_k/items/55d8fd1e7d42c8359a33)」でした。
よろしければ、ぜひ他の記事もご覧になっていってください。

## 【2024年7月 追記】登壇しました

本記事の内容を、デブサミ（Developers Summit 2024 Summer）で発表しました。

https://event.shoeisha.jp/devsumi/20240723/session/5087

スライドを公開したので、よかったらこちらも見てみてください。

https://speakerdeck.com/whisaiyo/hatarakuwole-siku-enzinianojian-kang-toxie-dong-wocu-jin-surushe-nei-ibentonoshi-zu-mitoji-shu

（以下はスライドの一部です）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/7f4f018e-f2c5-c520-767b-afef63c7cc90.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/f472c4bf-578f-c80e-f85e-3b8ca2db2705.png)

## はじめに

近年、リモートワークの普及に伴い、職場内のコミュニケーションの重要性が再認識されています。
職場における健康とコミュニケーションを促進する手段として、多くの企業が社内ウォーキングキャンペーンのような健康増進イベントを実施しています。
しかし、これらのキャンペーンを効果的に管理し、参加者間の交流を促進することは、特に大規模な組織にとっては容易なことではありません。

この記事では、私たちが実際に経験したウォーキングキャンペーンの運営上の課題と、それらに対処するために Google Apps Script（GAS）と Slack API をどのように活用したかについてご紹介します。
特に、チーム間のコミュニケーションを促進し、管理作業を効率化するために開発した自動化ツールに焦点を当てています。

この記事を通じて、「GAS と Slack でこんなことできるんだ！」と、みなさまの創造性を刺激するきっかけになれば幸いです。
また、テクノロジーが職場の健康促進活動をサポートし、コミュニケーションを豊かにする一例として、この記事が参考になればと思います。

## まとめ

- 全社ウォーキング大会やったら社員 750 人（全社員の 37%）が参加してくれた
- 少人数で運営を回すのに GAS × Slack Bot で各種 Bot を作った
  - チームチャンネル作成を自動化し、運用者の体験が向上した
  - またそのチャンネルを活用してチームごとにパーソナライズしたアナウンスや各メンバーのフォーム回答結果を共有することで、参加者の体験向上やコミュニケーション促進に貢献した
- 実装サンプルつくったので使ってみてください oOo

（チームチャンネル作成＆メンバー招待の様子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/deedca66-4d6e-78da-3da3-751aaae6e6d2.png)

（チームチャンネルへ毎週チームごとの入力シートを共有してピン留めする様子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/ad5a0032-53dc-faad-2060-d7be4f4de835.png)

（各メンバーの週次ふりかえりアンケート回答が共有された様子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/c56914ef-2e7b-eef8-3700-436285f5f139.png)

## この記事は

この記事では、Google Apps Script (GAS) と Slack API を用いた企業内イベント管理の実践的なアプローチを共有します。
具体的には、社内ウォーキングキャンペーンの効率的な運営と参加者のエンゲージメント向上を目指して、様々な技術的課題を解決した経緯を詳細に解説します。

私たちの目的は、以下の三つの主要な課題に対する具体的な解決策を提供することです。

1. **チームチャンネルの迅速な作成**
   遅延なくチームチャンネルを設立し、参加者がすぐにコミュニケーションを始められるようにする方法。

2. **重要情報のアクセス容易化**
   各チームの入力シートへのリンクをチャンネルにピン留めし、情報へのアクセスを簡単にする戦略。

3. **エンゲージメントの促進**
   チャンネル内での活動促進や参加者のモチベーション向上のためのテクニック。

記事全体を通して、GAS と Slack API を活用した具体的なコード例や、それらを実装する際に直面した課題（エラーハンドリング、レートリミットの対応、Slack チャンネル名の制限など）についても触れます。
また、実際の参加者からのフィードバックや、これらの技術的解決策がどのように実際の業務に役立つかについても言及します。

この記事は、技術的な背景を持つ読者はもちろん、企業内のイベントやプロジェクトを管理する立場の方々にも役立つ内容を目指しています。
GAS や Slack API に親しみのない方々にも、これらのツールがビジネスプロセスの改善にどのように貢献できるかを理解していただけると考えています。

## ウォーキングキャンペーンの課題

私が所属する [Works Healthy Project (WHP)](https://qiita.com/irongineer/items/70418bf4fa5f21672fc3#works-healthy-project-whp) は様々な健康増進イベントを実施しており、この全社ウォーキング大会 [Connected Walking](https://qiita.com/irongineer/items/70418bf4fa5f21672fc3#connected-walking-%E3%81%A8%E3%81%AF) は 3 年目のイベントとなります。
社内ウォーキングキャンペーンの運営では、いくつかの課題が浮き彫りになりました。
これらの課題は、参加者のエンゲージメントやイベントの効果を最大化する上で大きな障害となり得ます。

### 課題 1: チームチャンネルの作成までに時間がかかって交流の機会が遅れる

ウォーキングキャンペーンでは、参加者が小グループのチームに分けられ、それぞれのチームで Slack チャンネルを使用して交流します。
しかし、これらのチャンネルを手動で作成すると、数百人規模の参加者においては非常に時間がかかります。
**（今回の大会では参加者 750 名、147 チームでした・・・！）**
今までは「各チームの代表者がチャンネルを作成する」という方法を取っていましたが、それだとチームによってはチャンネル作成までに時間がかかる、場合によってはチームチャンネルが作成されないといった問題がありました。
その結果、キャンペーン開始直後から参加者が活発に交流できる環境が整うまでに時間がかかり、初期のエンゲージメントの機会を逃すことになります。

### 課題 2: チームごとの入力シートを探すのが面倒

キャンペーン参加者は、自身の歩数やを記録するための「歩数記録表」や各チームで挑戦する協力型ミッション「おさんぽ BINGO」という専用の Google スプレッドシートを使用します。
「歩数記録表」は全参加者共通のシートですが、参加者数が多いため縦にながーいシートになっており、かつキャンペーンの期間は 4 週間あって横にもながーいシートになっています。
「おんさぽ BINGO」はチームごとにシートが用意されており、毎週新しいお題の BINGO が発表されるため、アクセスすべきシートも毎週変わります。
これらにより、参加者が自分のチームのシートを見つけたり、入力したいセルにアクセスするのが困難でした。
参加者がデータを入力したいと思っても「あのシートどこにあるっけ？」と迷ってしまい継続のハードルが高くなりますし、参加者の貴重な時間を無駄な作業に費やすことになります。

### 課題 3: チームチャンネルを作っても交流のきっかけがない

チームチャンネルを設置したとしても、交流のきっかけをどのように作るかが課題です。
特に、初めての参加者や、普段あまりコミュニケーションを取らないチームメンバーにとっては、自発的に交流を始めるのが難しい場合があります。
そのため、チーム内のコミュニケーションを活性化させるための何らかの工夫が必要です。

これらの課題に対し、私たちはテクノロジーを活用した効果的な解決策を開発しました。
次のセクションでは、これらの課題に対する具体的な解決策と、それらを実現するための技術的アプローチについて詳しくご紹介します。

## 解決策

ウォーキングキャンペーンの成功を確実にするためには、前述の課題に対して効果的な解決策を講じる必要があります。
私たちは、これらの課題に対処するために、以下の三つの主要な解決策を採用しました。

### 解決策 1: チームチャンネルの作成を自動化する

チームチャンネルの迅速な作成は、参加者がキャンペーンを開始する上で非常に重要です。
このプロセスを自動化することで、キャンペーンの開始時にすべての参加者がすぐに交流を開始できるようになります。
また、我々 WHP は業務外プロジェクトとして少人数でこのウォーキングキャンペーンを運営しているため、チームチャンネルの作成を自動化することで、運営の負担を大きく軽減することもできます。
具体的には、Google Apps Script（GAS）と Slack API を組み合わせて、スプレッドシートに記載されたチーム情報に基づいて、必要なチャンネルを自動的に作成し、適切なメンバーを招待するスクリプトを実装しました。

（チームチャンネル作成＆メンバー招待の様子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/deedca66-4d6e-78da-3da3-751aaae6e6d2.png)

### 解決策 2: チームごとの入力シートへのリンクをチャンネルにピン留めする

各チームの入力シートへのアクセスを容易にするために、毎週チームごとの Google スプレッドシートへのリンクを上記で作成したチームチャンネルに共有し、Slack チャンネルにピン留めすることにしました。
これにより、参加者はチームチャンネル内で直接リンクをクリックするだけで、自分たちの入力シートにアクセスできるようになります。
このプロセスも GAS と Slack API を用いて自動化し、各チームチャンネルに対応するシートのリンクを自動的に投稿してピン留めするスクリプトを実装しました。

（チームチャンネルへ毎週チームごとの入力シートを共有してピン留めする様子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/ad5a0032-53dc-faad-2060-d7be4f4de835.png)

### 解決策 3: 各個人の週次ふりかえりアンケートの結果をチームチャンネルに共有する

チーム間の交流を促進するために、各個人が週次で行うふりかえりアンケートの結果をチームチャンネルに共有する選択肢を用意することにしました。
（今までのウォーキングキャンペーンでは、過去の記事で紹介した「[おつかれさま Bot](https://qiita.com/irongineer/items/70418bf4fa5f21672fc3#%E3%81%8A%E3%81%A4%E3%81%8B%E3%82%8C%E3%81%95%E3%81%BE-bot)」を実装して、参加者がふりかえりアンケートを提出すると、その結果を Bot 空のメッセージ (DM) で受け取るという仕組みを取っていました）
今回の試みにより、個々の参加者の活動や感想がチームメンバーと共有され、自然な会話のきっかけが生まれることが期待されます。
この共有プロセスも自動化し、アンケートの結果をチームチャンネルに投稿するスクリプトを実装しました。

（各メンバーの週次ふりかえりアンケート回答が共有された様子）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/c56914ef-2e7b-eef8-3700-436285f5f139.png)

これらの解決策により、ウォーキングキャンペーンの運営効率が大幅に向上し、参加者のエンゲージメントも高まりました。
次のセクションでは、これらの解決策を実現するために使用した GAS と Slack API の機能について詳しく説明します。

## GAS

[Google Apps Script (GAS)](https://www.google.com/script/start/) は、Google Workspace のサービスと連携し、自動化やカスタマイズされた機能を実現するための強力なツールです。
このスクリプト言語は、JavaScript に基づいており、スプレッドシート、ドキュメント、カレンダーなどの Google サービスと容易に連携できます。
私たちのウォーキングキャンペーンでは、GAS をイベントの起点として Slack API との連携を図り、様々な仕組みを実装しました。
具体的には以下のような API を利用しました。

### `UrlFetchApp.fetch`

[UrlFetchApp.fetch](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app?hl=ja) は、HTTP リクエストを送信するための GAS のメソッドです。
このメソッドを使用して、Slack API のエンドポイントにリクエストを送信し、チャンネルの作成やメッセージの投稿などの操作を行いました。
これにより、スプレッドシートのデータを基にして Slack 上での一連のアクションを自動化することができました。

### `SpreadsheetApp.openById`

[SpreadsheetApp.openById](https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app?hl=ja#openbyidid) メソッドを使用して、特定の Google スプレッドシートにアクセスしました。
これにより、ウォーキングキャンペーンの参加者リストやチーム情報などのデータを管理し、それを元にチャンネルの設定やメッセージのカスタマイズを行いました。

### `SpreadsheetApp.getSheetByName`

特定のシートにアクセスするためには、[SpreadsheetApp.getSheetByName](https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet?hl=ja#getSheetByName(String)) メソッドを使用しました。
これにより、特定のチームやイベントに関連する情報を取り出すことが可能になり、より細かい自動化が実現できました。

### `SpreadsheetApp.getRange`

[SpreadsheetApp.getRange](https://developers.google.com/apps-script/reference/spreadsheet/sheet?hl=ja#getRange(Integer,Integer)) メソッドは、スプレッドシート内の特定の範囲を選択するために使用されます。
このメソッドを用いて、必要なデータ範囲を抽出しました。
以下で説明する `SpreadsheetApp.getValues` メソッドや `SpreadsheetApp.setValue` メソッドと組み合わせて使用することで、スプレッドシート内のデータを取得したり、更新したりすることができます。

### `SpreadsheetApp.getValues`

[SpreadsheetApp.getValues](https://developers.google.com/apps-script/reference/spreadsheet/range?hl=ja#getvalues) メソッドは、特定の範囲の値を取得するために使用されます。
このメソッドを使用して、スプレッドシート内のデータを取得し、それをもとに Slack API を通じて各種操作を行いました。

### `SpreadsheetApp.setValue`

[SpreadsheetApp.setValue](https://developers.google.com/apps-script/reference/spreadsheet/range?hl=ja#setvaluevalue) メソッドは、特定の範囲に値を設定するために使用されます。
このメソッドを使用して、スプレッドシート内のデータを更新しました。

これらの GAS の機能を利用することで、ウォーキングキャンペーンの運営における時間と労力を大幅に削減し、参加者の体験を向上させることができました。
「実装」セクションでは、これらの機能を組み合わせた具体的な実装例を紹介します。

## 利用する Slack API

[Slack API](https://api.slack.com/) は、Slack の機能を拡張し、カスタマイズするためのツールです。
Slack API を使用することで、Slack チャンネルの作成やメッセージの投稿など、様々な操作を自動化することができます。
ウォーキングキャンペーンの運営を効率化し、参加者のエンゲージメントを高めるために、私たちはいくつかの Slack API エンドポイントを活用しました。
以下は、キャンペーンにおいて特に役立った Slack API の概要です。

### `conversations.create`

[converstaions.create](https://api.slack.com/methods/conversations.create) API は、新しいチャンネルを作成するために使用されます。
私たちは、各チームに対して専用のコミュニケーションチャンネルを自動的に設立するためにこの API を利用しました。

### `conversations.rename`

チームチャンネルの名前を変更するために、[conversations.rename](https://api.slack.com/methods/conversations.rename) API を使用しました。
この API を使用することで、チームチャンネルの名前を、チーム名に基づいて自動的に変更することができます。
（今回の記事では紹介しなかった「チーム表スプレッドシートに記載されたチーム名が更新された場合に、チームチャンネルの名前も自動的に更新する」というスクリプトで利用しました）

### `conversations.invite`

チームメンバーを新しく作成された Slack チャンネルに招待するために、[conversations.invite](https://api.slack.com/methods/conversations.invite) API エンドポイントを使用しました。

### `chat.postMessage`

[chat.postMessage](https://api.slack.com/methods/chat.postMessage) API エンドポイントは、Slack チャンネルにメッセージを投稿するために使用されます。
この API は最も頻繁に使用された API の一つで、チームチャンネルの作成や、チームごとの入力シートへのリンクの投稿、週次ふりかえりアンケートの結果の投稿など、様々な用途で使用しました。

### `pins.add`

重要なメッセージやドキュメントへのリンクをチャンネル内で目立つようにピン留めするために、[pins.add](https://api.slack.com/methods/pins.add) API を利用しました。
毎週、その週用の各チームごとの入力シートへのリンクをピン留めすることで、参加者が必要な情報に素早くアクセスできるようにしました。

### `pins.remove`

[pins.remove](https://api.slack.com/methods/pins.remove) API エンドポイントは、チャンネルからピン留めされたメッセージを削除するために使用されます。
毎週シートが更新されるため、ピン留めは最新週のものだけ残したく、過去のピン留めを削除するためにこの API を使用しました。

### `conversations.history`

[conversations.history](https://api.slack.com/methods/conversations.history) API エンドポイントは、チャンネルのメッセージ履歴を取得するために使用されます。
この API は様々な用途で使用しているのですが、今回紹介するスクリプトでは、チームチャンネルに投稿されたメッセージを取得し、過去のピン留めを削除するために使用しました。

### `files.upload`

[files.upload](https://api.slack.com/methods/files.upload) API エンドポイントは、Slack チャンネルにファイルをアップロードするために使用されます。
毎週「おさんぽ BINGO」の新しいお題を発表する際にわざわざシートへアクセスする必要がないよう、またウォーキング中にお題を確認するためにスプレッドシートを開く必要がないよう、お題を画像としてチームチャンネルに投稿するためにこの API を使用しました。

### `users.lookupByEmail`

[users.lookupByEmail](https://api.slack.com/methods/users.lookupByEmail) API エンドポイントは、メールアドレスを基に Slack ユーザーの情報を取得するために使用されます。
この API も最も頻繁に使用された API の一つで、スプレッドシートに記載されたメールアドレスを基に、Slack ユーザーの ID を取得し、チームチャンネルへのメンバー招待やメッセージ投稿時のメンションなどに使用しました。

これらの API の活用により、ウォーキングキャンペーンの運営が大幅に効率化され、参加者のエンゲージメントが高まることが期待されます。
次のセクションでは、これらの API を使用した具体的な実装について詳細に説明します。

## 実装

ウォーキングキャンペーンの課題に対応するために、我々は Google Apps Script（GAS）と Slack API を活用して、以下の実装を行いました。

### 実装 1: チームチャンネルの作成を自動化する

チームチャンネルの作成とメンバーの招待を自動化するために、Slack API の`conversations.create`と`conversations.invite`を利用しました。
GAS スクリプトは、スプレッドシートに記載されたチーム情報を読み取り、それに基づいて新しいチャンネルを作成し、適切なメンバーを招待する処理を行います。

#### 1-1. チーム情報を記載したスプレッドシートを作成する

まず、チームメンバー表情報が記載されたスプレッドシートを作成しました。
これは各参加者にイベントエントリー時に入力してもらうチームメンバー表を基にしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/27ae485a-db17-ab20-4310-868a8c3eb269.png)

このスプレッドシートには、以下のような情報（カラム）が記載されています。
```
#（チーム番号）	参加形式	チーム名	Slackチャンネル名	SlackチャンネルURL	#（メンバー番号）	社員番号	氏名	Div.	Dept.	Grp.	メールアドレス
```

なお、「Slackチャンネル名」と「SlackチャンネルURL」は最初は空欄としておき、スクリプトによって自動的に埋められるようにします。
また、「氏名」「Div.」「Dept.」「Grp.」は組織情報を参照するための情報で、スクリプトでは利用していません。削除してもらっても問題ありません。（スプレッドシートの VLOOKUP 関数を使って org シートの情報を参照し自動的に埋められるようにしています。

#### 1-2. GAS スクリプトを作成する

以下のような関数を作成しました。
詳細は GitHub にアップロードしているので、興味がある方はご覧ください。 // TODO: すみません、後ほどアップロードします・・・:bow:

```js
// ファイル上部に環境変数となる各マスタ情報を記載。GAS プロパティサービスによって代用することも可能
// （参考）[【GAS】コードにAPIトークンやIDのベタ書きを避ける（プロパティサービスの活用） #GAS - Qiita](https://qiita.com/massa-potato/items/2209ff367d65c5dd6181)

// ★要編集★ Columns master on 0-index
var ROW_HEADER = 3;
var COLUMN_TEAM_NUMBER = 0;
var COLUMN_ENTRY_TYPE = 1;
var COLUMN_TEAM_NAME = 2;
var COLUMN_SLACK_CHANNEL_NAME = 3;
var COLUMN_SLACK_CHANNEL_URL = 4;
var COLUMN_MEMBER_NUMBER = 5;
var COLUMN_EMPLOYEE_NUMBER = 6;
var COLUMN_MEMBER_NAME = 7;
var COLUMN_DIV = 8;
var COLUMN_DEPT = 9;
var COLUMN_GRP = 10;
var COLUMN_MEMBER_EMAIL = 11;

// teamNo をキーとして channelId を格納する連想配列
let channelIdMap = {};

function createAndInvite() {
  // Google Spreadsheetからデータを取得
  var sheet = SpreadsheetApp.openById(TARGET_GSS_ID).getSheetByName(TARGET_SHEET_NAME);
  var data = sheet.getDataRange().getValues();

  for (var i = ROW_HEADER; i < data.length; i++) {  // ヘッダ行をスキップ
    var row = data[i];
    var teamNo = row[COLUMN_TEAM_NUMBER];
    var teamName = row[COLUMN_TEAM_NAME] || "（未定）";
    var slackChannelUrl = row[COLUMN_SLACK_CHANNEL_URL];
    var memberNumber = row[COLUMN_MEMBER_NUMBER];
    var employeeNumber = row[COLUMN_EMPLOYEE_NUMBER];
    var memberEmail = row[COLUMN_MEMBER_EMAIL];

    if (memberEmail === "" && employeeNumber !== "") {
      // メンバーがいるのにメールアドレスがない場合は警告をログ出力し、スキップ
      console.warn("No member email found for team " + teamNo + " " + teamName + " member " + memberNumber);
      continue;
    } else if (memberEmail === "" && employeeNumber === "") {
      // メンバーがいない場合はスキップ
      continue;
    }

    // Slackチャンネル名の決定
    var resolvedChannelName = resolveCwTeamsChannelName(teamName, teamNo);

    // Slackチャンネルの取得（存在しない場合は作成）
    var teamChannelId = getOrCreateSlackChannel(teamNo, resolvedChannelName, slackChannelUrl, sheet, i);

    // メンバーを招待
    console.log(`Start inviteSlackChannel: #${resolvedChannelName} - ${memberEmail}`)
    inviteSlackChannel(teamChannelId, resolvedChannelName, teamName, memberEmail);
  }
}

// ファイル下部に各関数の定義を記載
```

<details><summary>各関数の定義（▶ を押すと展開します）</summary><div>

```js
function getChannelIdFromUrl(slackChannelUrl) {
  // SlackチャンネルのURLからチャンネルIDを取得
  var channelId = slackChannelUrl.split("archives/")[1];
  return channelId;
}

function getOrCreateSlackChannel(teamNo, slackChannelName, slackChannelUrl, sheet, i) {
  var cachedChannelId = channelIdMap[teamNo];
  console.log("Start getOrCreateSlackChannel: " + slackChannelUrl);
  // 連想配列に teamNo に対するチャンネルIDがあれば返却する
  if (cachedChannelId) {
    console.log("Channel already exists. Skip create channel: " + slackChannelName + " - " + cachedChannelId);
    return cachedChannelId;
  }

  // Slackチャンネルが存在する場合はチャンネルIDを返却する
  if (slackChannelUrl !== "") {
    var channelId = getChannelIdFromUrl(slackChannelUrl);
    console.log("Channel already exists. Skip create channel: " + slackChannelName);
    return channelId;
  }

  // Slackチャンネルの作成
  var createResponse = createSlackChannel(slackChannelName);
  if (createResponse.ok) {
    console.log(`Successfully created channel: #${slackChannelName}`);
    var channelId = createResponse.channel.id;

    // teamNo をキーとして channelId を連想配列に格納
    channelIdMap[teamNo] = channelId;

    // Google Spreadsheetを更新
    sheet.getRange(i + 1, COLUMN_SLACK_CHANNEL_NAME + 1).setValue(`#${slackChannelName}`);
    sheet.getRange(i + 1, COLUMN_SLACK_CHANNEL_URL + 1).setValue(`${SLACK_WORKSPACE_URL}/archives/${channelId}`);
    return channelId;
  } else {
    // エラーログ
    console.error('Channel creation failed:', createResponse.error);
    if (createResponse.error === 'name_taken') {
      var message = `<!channel>\nチームNo. ${teamNo}: #${slackChannelName} のチャンネル作成に失敗しました。\nチャンネル名が既に存在する可能性があります。\n既存のチャンネル名を変更してもらった後に再度実行してください。`;
      console.error(message);
      postMessageToSlack(SLACK_CHANNEL_ID_proj_whp_ウォーキング企画2023_企画メンバー用, message);
      return "ERROR";
    }
    console.error('Error object:', createResponse);
  }
}

function sanitizeTeamName(teamName) {
  // チーム名を処理し、記号を削除/置換し、80文字以内に短縮
  let sanitized = '';
  // 大文字アルファベットは小文字に変換
  sanitized = teamName.toLowerCase();
  // 日本語（ひらがな、カタカナ、漢字）、長音符（ー）、アルファベット、ハイフン（-）、アンダースコア（_）以外の記号を削除
  sanitized = sanitized.replace(/[^ぁ-んァ-ン一-龠ーa-zA-Z0-9-_]/g, '');
  // チャンネル名の固定文字列を除いて、80文字以内に短縮
  sanitized = sanitized.substring(0, 80 - SLACK_CHANNEL_NAME_PREFIX_LENGTH_CW_TEAM);
  return sanitized;
}

function resolveCwTeamsChannelName(teamName, teamNo) {
  // チーム名の処理
  var sanitizedTeamName = sanitizeTeamName(teamName);

  // もしチーム番号が 3 桁未満であれば、0 で埋める
  if (teamNo < 100) {
    teamNo = ("00" + teamNo).slice(-3);
  }
  // チャンネル名の固定文字列を含め、チーム名を結合
  const channelName = `z_connected_walking_2023_team${teamNo}_${sanitizedTeamName}`;
  console.log("Channel name: " + channelName);
  return channelName;
}

function inviteSlackChannel(channelId, channelName, teamName, memberEmail) {
  var userId = getUserIdByEmail(memberEmail);
  var inviteResponse = inviteMembersToChannel(channelId, [userId]);

  if (inviteResponse.ok) {
    console.log(`Successfully invited user: ${memberEmail} to channel: #${channelName}`);
    // ウェルカムメッセージの送信
    postWelcomeMessage(userId, teamName, channelId);
  } else {
    // エラーハンドリング
    handleInviteErrors(inviteResponse.error, channelId, userId);
  }
}

function postWelcomeMessage(userId, teamName, channelId) {
  var message = `<@${userId}> さん、チーム *${teamName}* へようこそ！\nConnected Walking チームチャンネル <#${channelId}> に招待しました。\nチームメンバー同士でコミュニケーションを取りながら楽しく歩きましょう！ :walk-fun:`;
  postMessageToSlack(channelId, message);
}

function handleInviteErrors(error, channelId, memberId) {
  // エラーハンドリングと通知
  if (error === 'user_is_restricted') {
    var message = `<!channel>\n<@${memberId}> さんを本チャンネル <#${channelId}> に招待しましたが、失敗しました。\nパートナー社員の場合はチームメンバーの方より適切な招待方法で招待をしてください。`;
    console.log(message);
    postMessageToSlack(channelId, message);
  } else if (error === 'already_in_channel') {
    console.log(`User: <@${memberId}> is already in channel: ${channelId}`);
  } else {
    console.error('Error:', error);
  }
}

```

</div></details>

### 実装 2: チームごとの入力シートへのリンクをチャンネルにピン留めする

各チームの Google スプレッドシートへのリンクをチャンネルにピン留めするために、`conversations.pin` API を使用しました。GAS スクリプトは、チャンネルごとに対応するスプレッドシートへのリンクを生成し、それをチャンネルに投稿した後、ピン留めする処理を自動化します。これにより、参加者が自分たちの入力シートに素早くアクセスできるようになり、記録の入力が容易になりました。


#### 2-1. チーム情報を記載したスプレッドシートを作成する

まず、チームメンバー情報とおさんぽBINGOの週次・チームごとのリンクが記載されたスプレッドシート「チーム用歩数記録表＆BINGOリンクのチャンネル投稿」を作成しました。
（歩数記録表から必要なカラムだけを [QUERY](https://support.google.com/docs/answer/3093343?hl=ja) 関数と [IMPORTRANGE](https://support.google.com/docs/answer/3093340?hl=en) 関数を利用して抽出してきています）
第1~4週の BINGO シートの生成 ＆ リンク記載は別の GAS スクリプトで実装しています（今回は紹介しません）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/7634c3cb-983f-62ab-7c0b-a717e4ba78fe.png)


このスプレッドシートには、以下のような情報（カラム）が記載されています。
```
#（チーム番号）	参加形態	チーム名	SlackチャンネルURL	#（メンバー番号）	氏名	メールアドレス	第1週	第2週	第3週	第4週
```

#### 2-2. GAS スクリプトを作成する

// TODO: すみません、後ほど書きます・・・土日にがんばる

### 実装 3: 各個人の週次ふりかえりアンケートの結果をチームチャンネルに共有する

参加者のエンゲージメントを高めるために、週次のふりかえりアンケートの結果をチームチャンネルに共有する機能を実装しました。
この目的のために、`chat.postMessage` API を使用し、GAS スクリプトを通じてアンケートの結果をチャンネルに投稿し、参加者間の交流を促進します。
時間の都合上、今回は紹介を省略しますが、以前私が執筆した以下の記事をベースに、今回作成したチームチャンネルに投稿するスクリプトを作成しました。

https://qiita.com/irongineer/items/70418bf4fa5f21672fc3

これらの実装を通じて、ウォーキングキャンペーンの管理が大幅に効率化され、参加者の体験が向上しました。
次のセクションでは、これらの実装を行う際に直面した課題とその解決策について説明します。

## 大変だったこと

ウォーキングキャンペーンの運営と自動化のプロセスでは、いくつかの技術的な課題に直面しました。これらの課題は、プロジェクトの成功に向けて重要な学びと経験をもたらしました。

### エラーハンドリング

Slack APIを使用する際、様々な種類のエラーに直面しました。
特に、`invalid_arguments` や `no_file_data` などのエラーは、APIリクエストのパラメータが不正であることを示していました。
これらのエラーに対処するためには、リクエストの構造を慎重に検討し、正しいフォーマットでデータを送信することが重要でした。
また、エラーレスポンスを適切に解析し、問題の原因を迅速に特定することが必要でした。

### レートリミット

Slack APIはレートリミットを設けており、一定期間内に許可されたリクエスト数を超えると制限がかかります。
特に、大規模な組織で多くのチャンネルを一度に作成しようとした際に、この問題に直面しました。
レートリミットに対処するために、リクエストを分散させるか、必要に応じて指数バックオフ戦略を採用しました。
// TODO: 後ほど追記します・・・ :bow: 

### Slack チャンネル名の制限

Slackではチャンネル名に一定の制限があります。
特に、使用できる文字の種類や長さに制限があるため、自動的にチャンネルを作成する際にはこれらの制限を考慮する必要がありました。
チャンネル名を生成する際には、スプレッドシート上のチーム名を適切に変換し、Slackの制限に適合する形に調整するロジックを実装しました。

```js
function sanitizeTeamName(teamName) {
  // チーム名を処理し、記号を削除/置換し、80文字以内に短縮
  let sanitized = '';
  // 大文字アルファベットは小文字に変換
  sanitized = teamName.toLowerCase();
  // 日本語（ひらがな、カタカナ、漢字）、長音符（ー）、アルファベット、ハイフン（-）、アンダースコア（_）以外の記号を削除
  sanitized = sanitized.replace(/[^ぁ-んァ-ン一-龠ーa-zA-Z0-9-_]/g, '');
  // チャンネル名の固定文字列を除いて、80文字以内に短縮
  sanitized = sanitized.substring(0, 80 - SLACK_CHANNEL_NAME_PREFIX_LENGTH_CW_TEAM);
  return sanitized;
}
```

他にも大変だったことがあったはずなので、思い出し次第追記します。
これらの障壁を乗り越え、無事にスクリプトを実装することができました。

## 参加者の声

ウォーキングキャンペーンの実施を通じて、参加者から多くのポジティブなフィードバックを受け取ることができました。


### チームチャンネル作成＆メンバー招待されたことに対する反応

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/bf7ea8f5-c50c-ead9-6132-849c918a33fb.png)

### チームチャンネルへ毎週チームごとの入力シートを共有してピン留めされたことに対する反応

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/a193822a-36dc-fae8-a079-c915e5203993.png)

### 各メンバーの週次ふりかえりアンケート回答が共有されたことに対する反応

   「週次アンケートの結果を共有することで、他の人たちの活動や考えを知ることができ、話題が増えました。」というコメントを複数いただきました。
   自動化されたアンケート結果の共有がチーム内コミュニケーションのきっかけとなったと言えるんじゃないかと思います。
   
これらの声は、技術的な取り組みが参加者の体験に直接的な影響を与え、プロジェクトの成功に貢献したことを示しています。
自動化と効率化の取り組みが、参加者の満足度を高め、より活発なコミュニケーションを促進したと言っていいんじゃないでしょうか。

## サンプル

（後ほど GitHub にスクリプトをアップして追記します :bow: ）

## まとめ（再掲）

- 全社ウォーキング大会やったら社員 750 人（全社員の 37%）が参加してくれた
- 少人数で運営を回すのに GAS × Slack Bot で各種 Bot を作った
  - チームチャンネル作成を自動化し、運用者の体験が向上した
  - またそのチャンネルを活用してチームごとにパーソナライズしたアナウンスや各メンバーのフォーム回答結果を共有することで、参加者の体験向上やコミュニケーション促進に貢献した
- 実装サンプルつくったので使ってみてください oOo

## 余談

なお、本実装を開始する際に、以下のように ChatGPT を利用してたたき台のスクリプトを作成してもらいました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110368/7a6315d4-e529-7432-57cd-774ba4976ae5.png)

あと、GitHub Copilot を活用するため、まずコメントを書き、Copilot によるサジェストでコード補完をしてもらいました。
（だからこの記事内のコードはコメントが多いです。人に説明するときにも楽だったので一石二鳥）
今回の要件は GAS と Slack API という汎用的なものであったため、GitHub Copilot はかなり有効に機能してくれました。

このように AI をフル活用したおかげで、私のようなマネージャーでコードを書く機会が少なくなった者でも、1 つのスクリプトを 1~2 日の業務外活動で完成させることができました。
いい時代になったものです。

## おわりに

いかがでしたでしょうか。
今回は「ウォーキング大会のふりかえり」をテーマにしましたが、この仕組み自体はどんなテーマでも転用が可能だと思います。
（ダイエット企画等のチーム制イベント、新卒オンボーディングのアンケートなど）
この記事がどなたかの参考になれば幸いです。

なお、今回紹介しきれなかった実装例で「各チャンネルへの投稿数・リアクション数を集計してランキングを作成する」というものもあります。
こちらは clasp を使って TypeScript で実装したり、ファイル分割・再利用を実現したり、ESLint, Prettier, Jest などのツールを導入したりと、より本格的な開発を行っています。
要望があるようであればこちらについても、別途記事にまとめたいと思います。

本記事は[株式会社 Works Human Intelligence](https://www.works-hi.co.jp/) の [アドベントカレンダー](https://qiita.com/advent-calendar/2023/advent-works-hi) の 22 日目の記事でした。

明日は [@Chrysanthemum94](https://qiita.com/Chrysanthemum94) さんの記事「[運用開発部門と教育部門を兼務して研修を作った話](https://qiita.com/Chrysanthemum94/items/e27a15ecb1dd5cf34996)」です。
ぜひご覧ください。