佐藤 あゆみ
佐藤 あゆみ

necco Note

Googleカレンダーの予定に応じてSlackのユーザー表示名を変える方法(Google Apps Script)

  • Workflow

neccoではチーム内のコミュニケーションツールとしてSlackを利用しています。リモート勤務のメンバーも複数おり、全員が同じ場所に出社する場合と比べると、メンバーの勤務状況が見えにくいという課題もありました。
この記事では、課題解決のために利用した、GoogleカレンダーとSlackをGoogle Apps Scriptで自動連携させる方法をご紹介します。

なぜ表示名を変更するのか

neccoでは、Slackのユーザー表示名に勤務状況を表記することで、各メンバーの勤務状況を共有しています。
例えば:

  • 通常勤務日:「Ayumi Sato」
  • 休暇日:「Ayumi Sato /本日休み」
休暇日のSlackでの表示。メンションにも休みであることが明記される。

本来であれば、こういったステータスの変更はSlack純正のステータス機能で表現する方が適切なのかもしれません。

Slack純正のステータス機能

しかしながら、上記のステータス機能を利用する場合、ステータスのアイコンにカーソルをホバーしないと状態が表示されず、メンション時にも状況が分からず不便なため、ステータスではなく表示名を変更するようになりました。

これまでは、Slackでのユーザー表示名の変更を、Slackのユーザー設定画面から各々が手動で行っていました。
しかしながら、手動変更には以下の問題がありました:

  1. 休暇日にもSlackを開く必要がある
  2. 勤務日に表示名の変更を忘れてしまうことがある
  3. 日々の作業として煩わしい

Google Apps Scriptを使って自動化する

これらの問題を解決するために、Google Apps Scriptを活用し、Googleカレンダーの予定に応じてSlackの表示名を自動で変更できるようにしました。

仕組みの概要

  1. Google Apps Scriptで定期的にスクリプトを実行する
  2. スクリプトがGoogleカレンダーの予定を確認する
  3. スクリプトから、Slack APIを使用して、予定に応じてユーザーの表示名を自動で更新する

Google Apps Script(GAS)とは

Google Apps Script(GAS)は、Googleが開発したアプリケーション開発プラットフォームです。 主に、Googleのサービスを自動化し、拡張するために利用されています。

GASはJavaScriptベースで開発でき、一般的なウェブの知識を活かして機能を構築できます。また、クラウドベースで動作するため、サーバーを用意することなくスクリプトを実行できます。

GoogleカレンダーなどのGoogle Workspaceの各サービスと連携でき、追加料金なく利用できるのが大きなメリットです。

前提条件

このスクリプトを動作させるには、下記の環境と権限が必要です。特に、GoogleカレンダーとSlackの連携には、それぞれのサービスでの適切な権限設定が必要になります。

Google Workspace関連

  • Google Workspaceを利用していること
  • 各メンバーがそれぞれのメールアドレスでGoogleカレンダーを所持していること
  • 休暇の際は各自がカレンダーに「佐藤公休」などとして予定を登録すること

Google Apps Script (GAS)関連

  • Google Workspace内のスプレッドシートでGASを実行できる権限
  • 実行ユーザーが各メンバーのGoogleカレンダーを閲覧できる権限
    • 実行ユーザーが各メンバーのカレンダーを自分のカレンダーに登録する必要があります(非表示でもOK)

Slack関連

  • Slackのadmin権限(Slackで他のユーザーの表示名を変更するために必要です)
  • Slack APIのUser OAuth TokenとBot User OAuth Tokenトークン(APIを使用するために必要です)

Slack APIのトークンを取得

下記よりSlack APIのアプリ作成画面を開き、Create New Appからアプリを作成します。

Slack API: Applications | Slack

スクラッチから作成を選びます。

任意のApp Nameと、スクリプトを動作させたいワークスペースを選択します。

Permissionsから、アプリの動作に必要なスコープを設定します。

Add an OAuth Scopeをクリックして、それぞれにスクリプトの動作に必要なスコープを付与します。

Bot Token Scopes に必要なスコープ

  • users:read
  • users:read.email

User Token Scopesに必要なスコープ

  • users.profile:write

ページを上にスクロールし、「Install to Workspace」をクリックして、ワークスペースにアプリをインストールます。

インストールできたら、再び「OAuth & Permissions」ページを開き、次のステップで利用するUser OAuth TokenとBot User OAuth Tokenの情報を控えておきます。

スプレッドシートの準備

まずは、Google スプレッドシートを開きます。

スプレッドシートに管理上、わかりやすい名前をつけておきます。

次に、スクリプトの実行に必要なSlackユーザー情報を記録しておくためのシートを用意します。 初期状態のシートの名称を「slack_users」に変更すればOKです。

スプレッドシートの1行目のA列からD列に、それぞれID、Display Name、Real Name、Emailと記入します(必須ではありません)

記入が終わったら、「拡張機能(1)」から、「Apps Script(2)」を開きます。

スクリプトプロパティの設定

Apps Scirptの設定(1)を開き、Slack APIのトークンを、GASのプロジェクトのスクリプトプロパティとして設定します。

Bot User OAuth TokenトークンをBOT_USER_AUTH_TOKEN、User OAuth TokenをUSER_AUTH_TOKENとして保存しましょう。また、ついでに、自社のメールアドレスとして利用しているドメインをCOMPANY_MAIL_DOMAINとして保存します(2)。

取得したトークン情報をそのままスクリプトに記述してももちろん動きますが、スクリプトプロパティとして別の場所に記述することで機密情報が露出するリスクを軽減できるほか、保守がラクになります。

GASスクリプト

以下のスクリプトを「コード.gs」に記述します。

スクリプトの概要

このスクリプトは主に以下の機能を持っています:

  1. Slackユーザーリストの取得と管理
  2. Googleカレンダーからの休暇情報の取得
  3. Slackの表示名の自動更新

それでは、各機能について詳しく見ていきましょう。

1. Slackユーザーリストの取得

スクリプトは最初にfetchSlackUsers()関数を使って、Slack APIから自社のメンバーのユーザー情報を取得します。取得したデータは、スプレッドシートに保存します。スプレッドシートに保存することで、スクリプトの実行時に最新のユーザー情報を簡単に参照できます。

2. Googleカレンダーからの休暇情報の取得

getTodaysEvents()関数で、各メンバーのGoogleカレンダーから今日のイベントを取得します。そして、isHolidayEvent()関数を使って、イベントのタイトルに「有給」や「休」という文字が含まれているかをチェックします。これらにより、メンバーが今日休暇を取っているかどうかを判断します。

3. Slackの表示名の自動更新

休暇情報に基づいて、updateDisplayNameWithHoliday()関数がSlackの表示名を更新します。休暇中のメンバーの表示名には「/本日休み」を追加し、休暇でない場合は休みを表す文字列が削除されます。

// 設定
const CONFIG = {
  BOT_USER_AUTH_TOKEN: PropertiesService.getScriptProperties().getProperty('BOT_USER_AUTH_TOKEN'),
  USER_AUTH_TOKEN: PropertiesService.getScriptProperties().getProperty('USER_AUTH_TOKEN'),
  COMPANY_MAIL_DOMAIN: PropertiesService.getScriptProperties().getProperty('COMPANY_MAIL_DOMAIN'),
};

// メイン関数:スクリプトの主要な処理を実行
function main() {
  try {
    const users = fetchSlackUsersFromSheet();
    users.forEach(user => {
      const userId = user[0];
      const calendarId = user[3]; // メールアドレスをカレンダーIDとして使用

      // 今日のイベントを取得
      const events = getTodaysEvents(calendarId);
      if (events === null) {
        Logger.log(`Calendar not found for user: ${userId}`);
        return;
      }

      // イベントに「有給」「休」が含まれているか確認
      const hasHoliday = events.some(event => isHolidayEvent(event));

      // 「休み」が含まれているかどうかで表示名を更新
      if (hasHoliday) {
        updateDisplayNameWithHoliday(userId, true);
      } else {
        updateDisplayNameWithHoliday(userId, false);
      }
    });
  } catch (error) {
    Logger.log(`Error in main function: ${error.message}`);
  }
}

// Slackユーザーから自社のメールアドレスを持つアカウントを抽出してスプレッドシートに記録する関数
function fetchSlackUsers() {
  const apiResponse = callWebApi(CONFIG.BOT_USER_AUTH_TOKEN, "users.list", {});
  const json = JSON.parse(apiResponse);

  const users = [];
  json.members.filter((m) => !m.deleted && !m.is_bot && m.profile.email && m.profile.email.endsWith('@' + CONFIG.COMPANY_MAIL_DOMAIN)).forEach((m) => {
    users.push([m.id, m.name, m.real_name, m.profile.email]);
  });

	// スプレッドシートにユーザー情報を記録
  const sheet = SpreadsheetApp.getActive().getSheetByName('slack_users');
  sheet.getRange("A2:D").clear();
  sheet.getRange(2, 1, users.length, 4).setValues(users);
}

// Slack Web APIを呼び出す汎用関数
function callWebApi(token, apiMethod, payload) {
  const response = UrlFetchApp.fetch(
    `https://www.slack.com/api/${apiMethod}`,
    {
      method: "post",
      contentType: "application/json; charset=UTF-8",
      headers: { "Authorization": `Bearer ${token}` },
      payload: JSON.stringify(payload),
    }
  );
  return response;
}

// スプレッドシートからユーザー情報を取得する関数
function fetchSlackUsersFromSheet() {
  const sheet = SpreadsheetApp.getActive().getSheetByName('slack_users');
  return sheet.getRange(2, 1, sheet.getLastRow() - 1, 4).getValues();
}

// 今日のイベントを取得する関数
function getTodaysEvents(calendarId) {
  const calendar = CalendarApp.getCalendarById(calendarId);
  if (!calendar) {
    return null;
  }
  const today = new Date();
  return calendar.getEventsForDay(today);
}

// イベント名に休みの情報を含むかを確認する関数
function isHolidayEvent(event) {
  const title = event.getTitle();
  return title.includes('有給') || title.includes('休');
}

// Slackの表示名を休み情報に基づいて更新する関数
function updateDisplayNameWithHoliday(userId, isHoliday) {
  const displayName = getSlackDisplayName(userId);
  const holidaySuffix = '/本日休み';
  let newDisplayName;

  // 「休み」を追加または削除するロジック
  if (isHoliday && !displayName.includes(holidaySuffix)) {
    newDisplayName = displayName + holidaySuffix;
  } else if (!isHoliday && displayName.includes(holidaySuffix)) {
    newDisplayName = displayName.replace(holidaySuffix, '');
  } else {
    return; // 変更が不要な場合は何もしない
  }

  // Slackの表示名を更新
  updateSlackDisplayName(userId, newDisplayName);
}

// Slackの表示名を取得する関数
function getSlackDisplayName(userId) {
  const url = `https://slack.com/api/users.info?user=${userId}`;
  const options = {
    method: 'get',
    headers: {
      'Authorization': `Bearer ${CONFIG.BOT_USER_AUTH_TOKEN}`
    }
  };
  const response = UrlFetchApp.fetch(url, options);
  const data = JSON.parse(response.getContentText());

  if (!data.ok) {
    throw new Error(`Slack API error: ${data.error}`);
  }

  return data.user.profile.display_name;
}

// Slackの表示名を更新する関数
function updateSlackDisplayName(userId, newDisplayName) {
  const url = '<https://slack.com/api/users.profile.set>';
  const payload = {
    user: userId,
    profile: {
      display_name: newDisplayName
    }
  };
  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': `Bearer ${CONFIG.USER_AUTH_TOKEN}`
    },
    payload: JSON.stringify(payload)
  };
  const response = UrlFetchApp.fetch(url, options);
  const data = JSON.parse(response.getContentText());

  if (!data.ok) {
    throw new Error(`Slack API error: ${data.error}`);
  }

  Logger.log(`Successfully updated display name to: ${newDisplayName}`);
}

スクリプトを実行する

まずはfetchSlackUsers()スクリプトを実行して、Slackからデータを取得してスプレッドシートに書き込みできるか試してみましょう。 初回の実行時には、Google Workspace等へのアクセス許可を求める画面が表示されますので、許可します。

スプレッドシートを開き、自社のメンバーの一覧を取得できていればOKです。

次に、main()関数を実行して、休暇中のユーザー名を変更できるか試します。

アクセス許可を求める画面が表示されたら、許可します。

休みの条件に該当するメンバーがいれば、下記のログが出て、実行が完了します。

Slackを開き、表示名を変更できていることを確認します。

スクリプトを定期的に実行する

動作を確認できたら、これらのスクリプトを自動的に実行させ、毎日自動で表示が変更されるようにします。

トリガーページを開き(1)、トリガーを追加(2)します。

main関数のトリガーを毎日0時台に実行し、fetchSlackUsers関数はそれよりも前に実行されるよう、23時台に実行しています。それほどメンバーの増減が多くないので、fetchSlackUsersは週に1度の実行に設定しています。

(下記のスクリーンショットではすでにトリガーが設定されている状態になっています)

実行する関数を選択し、時間主導型、日付ベースのタイマーを設定します。

上記で全ての設定が完了しました!

終わりに

本記事では、Google Apps Script(GAS)を使用してGoogleサービスとSlackを連携させる方法について解説しました。GASは、かんたんにスクリプトを定期実行でき、アイデアを気軽に試せるため、ちょっとした日常業務の効率化にはもってこいのツールです。

neccoでは他にも様々なタスクをGASで効率化しており、今後も記事を追加していく予定です!最近では、AIツールを利用して、お悩み相談ベースでコードを生成できるようになっており、一般にもますます活躍の機会が増えそうだと感じています。

みなさまもぜひ、お試しください!

佐藤 あゆみ

佐藤 あゆみ

Ayumi Sato

ニューヨーク生まれ。まもなく東京に移住し、1994年から2年間のオーストラリアでの生活を経て、ふたたび東京へ戻り、今も暮らす。1997年頃より趣味としてWeb制作を始め、以後も独学で学ぶ。 音楽専門学校中退後、音楽活動での成功を夢見ながら、PCパーツショップやバイク輸出入会社、楽器店など、掛け持ち含めて計20以上(?)の業種でアルバイトを重ね、ECサイトの運営管理や自社サーバの管理、プログラミングなども学ぶ。音楽活動を展開する中で、集客やフライヤー制作、プロモーションビデオ制作を行い、周辺技術を身につけるきっかけとなるも、2011年頃に区切りをつけ、ウェブ制作で生計を立てることを決意。その後は画廊やウェブ制作会社などで勤め、2014〜2022年まではフリーランスとして活動。2018年より、CSS NiteやBAU-YAなどのイベント、スクールにて登壇。2019年に「HTMLコーダー&ウェブ担当者のためのWebページ高速化超入門」を出版。 趣味はガジェットいじり&新しいサービスを試すこと。

SHARE

Other Note

necco Note

こんなにできてなかったんだ…アシスタントが「いちばん大切なのに誰も教えてくれない段取りの教科書」を読んで学んだ仕事の取り組み方