こんにちは、和尚です?
前回の”Growth by CaseStudy①「クライアントとのリレーション形成」“に引き続き、
今回もクライアントとのグロース開発をメインで行うGrowth by グループの取り組みを紹介する、
Growth by CaseStudyの第2弾「社内MTGの効率化」について紹介させて頂きます!
昨年2020年よりコロナの影響で、リモートワークが増えてオンラインMTGを行なっている企業様も多いと思います。
そこで多くの方々が頭を悩ませているのが、ちょっとした軽い相談をする時では無いでしょうか。
リアル出社の時は「ちょっと相談あるんだけど…」と気軽に声を掛ける事ができた行為が、オンラインMTGになると、まずはオンライン会議室用のルームを作成して、チャットツールで連絡を取って、そこから「相談があるんだけど…」と連絡する流れとなりますが、「オンライン会議用ルームの作成」が億劫であると言う声を多く耳にします。
(作っても「今忙しいんで…」と打ち合わせが出来ず、作り損になってしまう可能性もあるので)
そうした「ちょっとした相談」を「なるべくシンプルに」できる方法は無いかという命を受けまして、こちらを作成しました!!
サクッとMeet!(/meet)

概要
サクッとMeet!(/meet)は、bravesoftが活用するGoogle MeetのURLを、同じくbravesoftが活用するSlackから発行できるコマンドです。
入力フォーマットには簡易型と詳細型の2種類を用意して、それぞれ状況に合わせて使っていただくようにしました。
簡易型コマンド
「/meet」
こちらはコマンドのみのパターンです。
お好きなチャンネルで上記のコマンドを打つと、このシステム専用のSlackアプリからMeetのURLが発行されます。
※ 自作コマンドはスレッドにはまだ対応していないので現状ではコメント欄以外から使うことはできません。Slack開発陣によるとそのうち対応するとかしないとか。。。

発行者のGoogleカレンダーにコマンドを実行した時間から15分間スケジュールが登録されるようになっています。なので他のブレイバー(※弊社の従業員)から作成者がサクッとMeet!中だということがわかるようになっています

詳細型コマンド
「/meet ${会議名} ${何分後から会議をスタートするか} ${@山田一郎} ${@佐藤二郎}…」
コマンドの後に続けてスケジュール詳細を設定できるパターンです。
第一引数には「会議名」、
第二引数には「Googleカレンダーに登録するスケジュールの時間(現時点から何分後か)」、
第三引数以降に「スケジュールに招待する人(Slackのメンション)」を選ぶことが出来ます。
簡易型の場合はGoogleカレンダーに登録されるユーザーは作成者だけですが、詳細型で第三引数以降にMTGに参加して欲しい人を入れた場合、その人たちも予定の参加者に追加することが可能になっています✌️

開発について
今回このコマンドを作るに当たって、いくつか初めての挑戦をしてみました。これまでもこの手の簡易的なシステムを組むのにGAS(Google App Scripts)を利用してきましたが、今回はブラウザでのコーディングではなくTypeScriptを使ってローカルでのコーディングに挑戦しています!
GASはJavascriptをベースとした専用言語で書かかれています。なのでTypeScriptで書くには ClaspというGoogle公式CLIツールを使用してコンパイルする必要があります。このClaspはコンパイルだけではなくプロジェクトに直接デプロイすることも可能なため、とても便利です。
また、従来のブラウザでのコーディングからローカルの開発になったことで、Git管理することが出来たりLintやUnitTestなどのサポートツールなども簡単に利用することができたりと非常に便利になりました。
ではソースコードを見ていきましょう!
ソースコードについて
index.ts
import Slack from './slack'
import Meet from './meet'
/**
* Postリクエスト受信
* @param e
*/
function doPost(e): GoogleAppsScript.Content.TextOutput {
const args: string[] = e.parameter.text.split(' ');
const slack = new Slack();
const meet = new Meet();
let slackIds: string[] = [];
if (!isSimpleCommand(args)) {
slackIds = args
.filter((_, index) => index != 0 && index != 1)
.map(arg => arg.slice(1));
}
slackIds.push(e.parameter.user_name);
const userEmails: string[] = slack.getEmailsBySlackID(slackIds);
let hangoutLink: string;
let responseText: string;
if (isSimpleCommand(args)) {
hangoutLink = meet.createMeet('サクッとMeet!', userEmails, 0);
responseText = `?Meetでサクッと話そう! ${hangoutLink}`;
} else {
if (userEmails.length > 0) {
const summary: string = args[0];
const startAfterMinute: string = args[1];
hangoutLink = meet.createMeet(summary, userEmails, Number(startAfterMinute));
responseText = `${summary} ${hangoutLink}`;
} else {
responseText = 'Error: User not found.';
}
}
let response = {
response_type: 'in_channel',
text: responseText
};
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(
ContentService.MimeType.JSON
);
}
function isSimpleCommand(args: string[]) {
return args.length <= 2;
}
meet.ts
export = Meet;
interface MeetInterface {
/**
* meet部屋作成
*/
createMeet(summary: string, userEmails: string[], startAfterMinute: number): string
}
class Meet implements MeetInterface {
createMeet(summary: string, userEmails: string[], startAfterMinute: number): string {
const now: Date = new Date();
const startDate: Date = new Date(now.getTime() + (startAfterMinute * 60000));
const endDate: Date = new Date(startDate.getTime() + 900000);
const jsonUsers: object[] = userEmails.map((userEmail) => {
return { email: userEmail }
})
const detail: object = {
summary: summary,
start: {
dateTime: startDate.toISOString()
},
end: {
dateTime: endDate.toISOString()
},
attendees: jsonUsers,
conferenceData: {
createRequest: {
requestId: Math.random().toString(32).substring(2),
conferenceSolutionKey: {
type: "hangoutsMeet"
},
}
}
};
return Calendar.Events.insert(detail, 'primary', { conferenceDataVersion: 1 });
}
}
slack.ts
export = Slack;
interface SlackInterface {
/**
* slackからメアドを取得する
* @param ids
*/
getEmailsBySlackID(ids: string[]): string[]
}
enum SLACK_API_PATH {
usersList = 'users.list'
}
class Slack implements SlackInterface {
private readonly DOMAIN: string = 'https://slack.com/api/';
private readonly TOKEN: string = 'SLACK_TOKEN'; // キー情報なので「PropertiesService.getScriptProperties().getProperty()」などを利用してください。
/**
* ターゲットのEmailを取得する
* @param ids
*/
getEmailsBySlackID(ids: string[]): string[] {
let members: object[] = this.getMembers();
let emails: string[] = [];
Object.keys(members).forEach((key) => {
if (ids.includes(members[key].name)) {
let email: string = members[key].profile.email;
emails.push(email);
}
})
return emails
}
/**
* Slackメンバーを取得する
*/
private getMembers(): object[] {
const options: object = {
method: "GET",
payload: { token: this.TOKEN }
};
const response: any = UrlFetchApp.fetch(
this.DOMAIN + SLACK_API_PATH.usersList,
options
);
return JSON.parse(response).members;
}
}
GASヘデプロイが完了したら、
- デプロイしたものをウェブアプリとして公開して、URLを発行します(事前に「Google Calendar v.3」の追加を忘れずに!)
- Slackアプリを作成
- GASで発行したURLをコマンドの設定と共に設定
- 権限の追加「chat:write」「commands」「user:read」「user:read:email」
- ワークスペースにアプリを追加で完了です!
あとがき
実を言うとこのシステム自体は上司が「こんな機能欲しい〜誰か作って〜」という一言から、自分のTypescriptの勉強の一環として作ったものでした?
こういった簡単なシステムは良い勉強の材料になりますし、上司や先輩などの何気無い一言をよーく聞いてみるとネタが急に降ってくるかもしれません(サクッとmeet!はみんなが便利になったので一石X鳥???
是非、自分たちの会社にあった「サクッと!Meet」を作ってみてください!
おまけ
「サクッとMeet!」など色んなものを作っていたら、2021年上期のアイデア賞を頂きました✌️(受賞後の座談会はコチラ)

投稿者プロフィール

最新の投稿
テクノロジー・専門技術2022年10月11日iOS16をサポートしよう!
テクノロジー・専門技術2021年12月23日Combine初心者講座 -SwiftUIの相棒を使いこなそう-
技術・開発情報2021年9月14日Growth by CaseStudy②「社内MTGの効率化〜slackコマンドの有効活用〜」
テクノロジー・専門技術2021年8月20日Apple TV (tvOS) アプリについて