LoginSignup
11
5

SlackBotに各人毎のGoogle MeetのURLを出してもらう

Last updated at Posted at 2020-12-12

突然ですがこんな経験はないですか?

Slack上でのやりとりでは限界があってビデオ通話で会話がしたいときに

  1. Slackで相手にMeetしましょうと声を掛ける
  2. ブラウザで新しいタブ開く
  3. MeetのURLを作成する
  4. Slackに戻ってMeetのURLを共有してルームに入る

という流れ

画面移動とか地味にめんどくさくないですか?
僕はめんどくさいです
なのでSlackBotに自分専用のMeetのURLを出してもらうことにしました

実際に動いてるもの

塗りつぶし多くてちょっと見辛いですがslackから移動する必要がなくMeetのURLを共有できます

スクリーンショット 2020-12-09 2.01.14.png

どうやっているのか

  1. Slack
  2. GAS
  3. Sheets API / Calendar API
  4. Slack

という流れでGAS経由でスプレッドシートで管理している個人ごとのURLを返します
スプレッドシートにURLがない場合はカレンダーAPIを使ってMeetのURLを生成してスプレッドシートに保存して返します
サーバレスで、GASは無料ホスティングできてとってもお手軽

設定していく

さっそくやってみたい!と思ってくれた人がいるかもしれないと信じてなるべく丁寧に手順書いていきます

1. Slackからの認証のためにGASを仮置きする

1-1. GoogleDriveで適当に右クリックしてGoogle Apps Scriptを作る

function doPost(e) {
  return ContentService.createTextOutput(JSON.stringify(e));
}

スクリーンショット 2020-12-12 17.52.26.png

1-2. GASをアプリとして公開する

ウェブアプリケーションとして導入 を選択
スクリーンショット 2020-12-12 17.47.00.png
スクリーンショット 2020-12-12 17.47.39.png
スクリーンショット 2020-12-12 17.47.47.png

ここで最後に発行されたURLをメモっておく

2. SlackBotを作る

2-1. https://api.slack.com/apps にアクセスしCreate New App からBotを作る

スクリーンショット 2020-12-09 12.42.42.png

スクリーンショット 2020-12-09 12.44.38.png

2-2. 左のメニューの OAuth & Permissions から Scopeを設定する

Bot Token Scopes

  • app_mentions:read
  • chat:write

を追加する

スクリーンショット 2020-12-09 12.43.38.png

2-3. 続けて、アクセストークンを発行する

スクリーンショット 2020-12-12 18.08.39.png
スクリーンショット 2020-12-12 18.08.57.png

  • Install To Workspace を押す
  • Botのアクセストークンが発行されるのでメモっておく

2-4. 左のメニューの Event Subscriptions から イベントの設定とGASエンドポイントの認証をする

スクリーンショット 2020-12-12 17.49.36.png

  • Enable Events をOnにする
  • 1-2で発行したGASアプリのURLを Request URL へ入力する(成功すれば Verified になる)
  • Subscribe to bot eventsapp_mention を追加する
  • Save Changes で保存

3. スプレッドシートを作る

3-1. GAS同様に適当にドライブにスプレッドシートを作成する

3-2. URLからスプレッドシートのIDをメモる

https://docs.google.com/spreadsheets/d/*************/edit#gid=0
*************の部分がIDとなる
GASで使うのでメモっておく

4. Google CalendarでAPI経由でMeetのURLを作るために適当にカレンダーを作る

4-1. 新しいカレンダーを作成 から適当にカレンダーを作る

https://calendar.google.com/calendar/u/0/r/settings/createcalendar にアクセスして作る

スクリーンショット 2020-12-12 21.09.39.png

4-2. カレンダーに登録 からさっき作ったカレンダーIDを確認する

https://calendar.google.com/calendar/u/0/r/settings/addcalendar にアクセスして確認

  • さっき入力したカレンダー名でを入力して選択するとカレンダーの選択画面が出る

スクリーンショット 2020-12-12 21.14.13.png

スクリーンショット 2020-12-12 21.09.55.png

  • 下にスクロールするとカレンダーIDが確認できるのでメモしておく

スクリーンショット 2020-12-12 21.06.55.png

5. GASを修正

5-1. スクリプトを修正 ※1〜4を適宜置換

const slackBotAccessToken = '※1 2-3で発行したSlackBotのアクセストークン';
const spreadSheetId = '※2 3-2でメモっておいたスプレッドシートのID';
const calendarId = '※3 4で作った作ったカレンダーのID@group.calendar.google.com';
const attendees = [ { email: '※4 適当に自分のGsuiteのメアド' } ];

function doPost(e) {
  const slackApp = SlackApp.create(slackBotAccessToken);
  const contents = JSON.parse(e.postData.contents);

  // slackの3秒タイムアウトリトライ対策
  let cache = CacheService.getScriptCache();
  if (cache.get(contents.event.client_msg_id) == 'done') {
    return ContentService.createTextOutput();
  } else {
    cache.put(contents.event.client_msg_id, 'done', 600);
  }

  // スレッドの中からだったらスレッドに返信する
  const opt = contents.event.thread_ts ? { thread_ts: contents.event.thread_ts } : {};
  slackApp.postMessage(contents.event.channel, buildResponseMessage(contents.event.user), opt);

  // 最初の認証でchallengeを返すのに必要だった
  return ContentService.createTextOutput(JSON.stringify(e));
}

// select or upsert してメッセージ返す
function buildResponseMessage(userId) {
  const sheet = SpreadsheetApp.openById(spreadSheetId).getSheets()[0];
  let url = '';
  
  // selectして
  url = selectUrl(sheet, userId);
  // なければ作ってinsert(upsertなのは名残)
  if (!url) {
    url = generateMeetUrl();
    upsertUrl(sheet, userId, url);
  }

  return `${mention(userId)} ${url}`;
}

function getUserIdRange(sheet, userId) {
  let matchedRange = sheet.createTextFinder(userId).matchCase(true).matchEntireCell(true).findNext();

  return matchedRange;
}

// select
function selectUrl(sheet, userId) {
  // userIdで探す
  let matchedRange = getUserIdRange(sheet, userId)
  
  // userIdヒットしなければnull返す
  if (!matchedRange) return null;

  // userIdヒットしてもURLが空文字ならnull返す
  let url = matchedRange.offset(0, 1).getValue();
  if (!url) return null;

  // 前回のアクセス日が空 || 180日以上経過したら無効判定としてURL更新させる
  // meetのURLの有効期限は365日
  let lastDate = matchedRange.offset(0, 2).getValue();
  if (!lastDate || dayDiff(todayYmd(), lastDate) >= 180) return null;

  // 今日の日付で更新
  matchedRange.offset(0, 2).setValue(todayYmd());

  return url;
}

function mention(userId) {
  return `<@${userId}>`;
}

// upsert
function upsertUrl(sheet, userId, url) {
  let matchedRange = getUserIdRange(sheet, userId)

  if (!matchedRange) {
    // なければinsert
    sheet.appendRow([userId, url, todayYmd()]);
    return;
  }

  // あればupdate
  matchedRange.offset(0, 1, 1, 2).setValues([[url, todayYmd()]]);
}

// カレンダー作ってMeetUrlとって消す
function generateMeetUrl() {
  let event = Calendar.Events.insert(
    {
      start: { dateTime: new Date('2020/10/17 10:00:00').toISOString() },
      end: { dateTime: new Date('2020/10/17 12:00:00').toISOString() },
      attendees,
      conferenceData: {
        createRequest: {
          conferenceSolutionKey: {
            type: "hangoutsMeet"
          },
          requestId: 'リクエストID',
        },
      },
    },
    calendarId,
    { conferenceDataVersion: 1 },
  );

  const meetsUrl = event.hangoutLink;
    
  Calendar.Events.remove(calendarId, event.id)
  
  return meetsUrl;
}

function todayYmd() { 
  return toYmd(new Date());
}

function toYmd(date) {
  let y = date.getFullYear();
  let m = ('00' + (date.getMonth()+1)).slice(-2);
  let d = ('00' + date.getDate()).slice(-2);
  return (y + '-' + m + '-' + d);
}

function dayDiff(date1, date2) { 
  return ((new Date(date1) - new Date(date2)) / 86400000);
}

5-2. SlackAppライブラリを入れる

Slack BotをGASでいい感じで書くためのライブラリを作った
M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO

スクリーンショット 2020-12-12 22.53.52.png

スクリーンショット 2020-12-12 22.54.11.png

5-3. Google Calendar のAPIを叩けるようにする

スクリーンショット 2020-12-13 0.21.40.png

スクリーンショット 2020-12-13 0.21.49.png

5-4. デプロイする

  • 最初に仮置きした時と同じ手順で
  • Project versionNew を選択してリリース
    スクリーンショット 2020-12-12 21.19.16.png

6. あとはslackでbotをチャンネルに追加してメンション飛ばせばurlをくれる!

社内でもユーザがだいぶ増えてスプレッドシートはこんな感じになってます
スクリーンショット 2020-12-13 0.59.46.png

TODO

  • GASの権限などが実装者(自分)依存になっているので自分が退職してGSuiteアカウント消えても動き続けるようにする必要がある
  • がんばればTerraformでコード管理できそうなきがする
11
5
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
11
5