takanakahiko’s blog

多分三日坊主で辞めます。

Google Apps Script を用いてカレンダーへの自発的な参加登録ができるWebアプリを作成する

こんにちは。 takanakahiko です。

今回は、 Google Apps Script を用いてカレンダーへの自発的な参加登録ができるWebアプリを作成していきたいと思います。

このエントリーはAkatsuki Advent Calendar 2022の2日目の記事です。 adventar.org

昨日は tkmruさんの「脆弱性診断とiOSアプリの再署名」でした。 脆弱性診断に必要な再署名といった複雑な作業を簡単に行えるツールを公開されています。 hackerslab.aktsk.jp

前置き

今回紹介する実装例は Google Workspace ドメインに属している場合にのみ正しく動作します。 個人の Google アカウントでは動作しないためご了承ください。

また、 Google Apps Script に関する初歩的な説明は行いませんのでご了承ください。

モチベーション

私の業務において Google Calender で任意参加の予定を作成する機会がありました。 私が開発や運用を担当している基盤の利用者向けミーティングだったのですが、内容としては「参加したい人がいたらぜひ」といった温度感のものでした。 これ以外にも、例えば社内勉強会の予定なども同様のユースケースになると思います。

予定の参加希望者をカレンダーに招待する必要があるのですが、ざっくり考えると以下の方法があると思います。

  • 全社員向けに招待を飛ばす
  • 参加表明した人を Google Groups に招待し、その Group 向けに招待を飛ばす
  • 参加表明する人を一人ずつ招待する

しかしこれらの方法は、不必要な人への招待が発生してしまったり、また手動オペレーションが煩わしかったりでピンとくるものがありませんでした。

そこで、今回は Google Apps Script で軽い Web アプリケーションを作成し、それを利用することで解決したいと思いました。

Google Apps Script とは

Google Apps Script (以降は GAS の略称で表記)は Java Script スクリプトプラットフォームです。 GAS を用いると Google から提供されるさまざまなサービスを自動化することが可能です。

developers.google.com

また、GAS は

  • Spread Sheet のマクロにする
  • Web アプリケーションとして提供する

など、さまざまな呼出し方法があることも大きな特徴です。

実装

実際に実装する際のポイントを踏まえて説明していきます。

予定への招待

繰り返し予定への招待は以下のように行います。

var eventSeries = CalendarApp.getEventSeriesById(eventSeriesID);
eventSeries.addGuest(email);

ここで必要になるのが eventSeriesID を取得する方法です。 まず Google Calender で作った予定をダブルクリックします。 そうすると以下のような URL で編集画面が開くはずです。

ttps://calendar.google.com/calendar/u/0/r/eventedit/NTNnamxxxxxxxxxxxxxxxx

この中の NTNnamxxxxxxxxxxxxxxxx の部分は eid と呼ばれるものです。 これは base64 encode された文字列です。 これをデコードしてみると、以下のような文字列が取得できます。

53gjexxxxxxxx xxxxxx@xxxxx.xxx

ここにある 53gjexxxxxxxxeventSeriesID で、 xxxxxx@xxxxx.xxx が主催者のメールアドレスになります。 それを踏まえると、以下のような処理で eventSeriesID の取得も含めて招待まで行うことができることになります。

var eid = 'NTNnamxxxxxxxxxxxxxxxx'; // カレンダーの編集URLから取得
var eventSeriesID = Utilities.newBlob(Utilities.base64Decode(eid)).getDataAsString().split(' ')[0];
var eventSeries = CalendarApp.getEventSeriesById(eventSeriesID)
eventSeries.addGuest(email)

アクセスしたユーザの情報を取得する

上記の例にある email の取得方法について説明していきます。

GAS で実装した Web アプリにアクセスしたユーザのメールアドレスは以下のように取得できます。

var email = Session.getActiveUser();

しかし、 Session.getActiveUser() は基本的には Google Workspace 内でしか動作することができません。 この記事の冒頭にもありますが、今回の実装は Google Workspace 組織向けの実装となっています。 developers.google.com

ユーザがすでにイベントに招待されているかどうかは以下のように判断することができます。

if (eventSeries.getGuestByEmail(email)) {
  // 参加済み
}else{
  // 未参加
}

doGet から doPost へ POST するフォームを作成する

GAS では Web アプリケーションにアクセスしたユーザの HTTP メソッド(POSTやGET)によって呼ばれる関数が異なります。 以下のように HtmlService.createHtmlOutputContentService.createTextOutput を用いることでユーザに表示する内容を指定することができます。

function doGet(e) {
  // HTMLを表示する場合は以下のようにすると良い
  return HtmlService.createHtmlOutput(`<p>GETでアクセスされたよ</p>`);
}

function doPost(e) {
  // 文字を表示したいだけなら以下のようにしても良い
  return ContentService.createTextOutput("POSTでアクセスされたよ");
}

一般的なWebアプリケーションでは、GET によるアクセスで情報の閲覧を、 POST によるアクセスでユーザが何かしらの操作を行えるようにします。 言い換えると、 GET によるアクセスでいきなり操作(今回で言うところの予定の参加登録)が完了してしまったらアプリケーションとしては不自然です。

今回作るアプリケーションもそのように作っていきましょう。 GET でアクセスしたユーザには参加登録をするためのボタン(フォーム)を表示し、そのボタンを押したユーザーは POST でアクセスし直して参加登録がされる、といった流れにします。

ここで、問題があります。 GAS は HTML を iframe に包んで返却するといった点です。 iframe 内にある form で submit しても Forbidden Error 403 が返却されます。これは iframe 内での遷移になってしまうためです。 また、iframe の src が xxxx.googleusercontent.com に書き変わってしまうため、 <form action="." method="post" target="_top">> のよう action="."xxxx.googleusercontent.com を参照してしまい正常に動作しません。

それを踏まえて、以下のように action="${ScriptApp.getService().getUrl()}"target="_top" を設定することで iframe 外での post アクセスかつ、 POST 先の URL が正しく設定されているため正常に動作します。

function doGet(e) {
  return HtmlService.createHtmlOutput(`
    <form action="${ScriptApp.getService().getUrl()}" method="post" target="_top">
      <input type="submit" value="submit" />
    </form>
  `);
}

function doPost(e) {
  return ContentService.createTextOutput("POSTでアクセスされたよ");
}

実装の全体

これらの要点を踏まえ、今回は以下のように実装しました。

function getEventSeries() {
  var eid = 'xxxxxxxxxxxxxxx'; // カレンダーの編集URLから取得
  var eventSeriesID = Utilities.newBlob(Utilities.base64Decode(eid)).getDataAsString().split(' ')[0];
  return CalendarApp.getEventSeriesById(eventSeriesID);
}

function doGet(e) {
  var eventSeries = getEventSeries();
  return HtmlService.createHtmlOutput(`
    <form action="${ScriptApp.getService().getUrl()}" method="post" target="_top">
      <input type="submit" value="${eventSeries.getTitle()}に参加登録する" />
      <span>(登録解除はカレンダーからお願いします)</span>
    </form>
  `);
}

function doPost(e) {
  var email = Session.getActiveUser();
  var eventSeries = getEventSeries();
  if (eventSeries.getGuestByEmail(email)) {
    return ContentService.createTextOutput("既に参加しているようです(登録解除はカレンダーからお願いします)");
  }else{
    eventSeries.addGuest(email);
    return ContentService.createTextOutput("参加登録しました(登録解除はカレンダーからお願いします)");
  }
}

公開

次に、書いた GAS をデプロイし、 Web アプリケーションとして公開、 URL を取得する必要があります。

ここで注意点として、一度も実行していない GAS をいきなりWeb アプリケーションとして公開することはおすすめできません。 それは、この GAS プロジェクトにカレンダー取得等の権限が付与されていない可能性が高いためです。 今回は、保存後に getEventSeries を選択して一度実行しておきましょう。

getEventSeriesを実行する

では Web アプリケーションとして公開します。 デプロイ時に「次のユーザとして実行」を「自分」に、「アクセスできるユーザー」を「〇〇内の全員」にします。 前者はカレンダーの招待を行うユーザーが自分であるため、後者は今回のユースケースでは社内に向けた予定であるためですね。

デプロイボタンを押す

ウェブアプリを選択する

各種項目を設定する

動作確認

試しにこのイベントに招待するやつを作りましょう。

なんかいい感じのミーティングと題した予定

デプロイ時に表示されたURLにアクセスすると

フォームが表示されている

ポチッとすると

参加登録できた旨が表示される

無事に招待されています

自分が予定のゲストとして登録されている

まとめ

Google Apps Script を用いてカレンダーへの自発的な参加登録ができるWebアプリを作成しました。 GAS 特有の仕様による罠も多いですが、それでもこのようなアプリケーションがこんなに手軽に作成できるのは素晴らしいですね。

最後まで読んでいただきありがとうございました。

明日の Akatsuki Advent Calendar 2022 は Yoshitomo Yasuno さんの Rails の inverse_of の挙動についての内容です。かなりニッチそうで個人的に楽しみです。

最後に、アカツキでは一緒に働くエンジニアを募集しています。 カジュアル面談もやっていますので、気軽にご応募ください。

hrmos.co

「賃貸を選ぶ上での知見をインターネットで聞いた結果」と実際

こんにちは。

3年ぐらい前、社会人を始めると同時に一人暮らしを始めました。 その際にインターネットの人たちにアドバイスをいただき、それをまとめた記事を執筆しました。

takanakahiko.hatenablog.com

2ヶ月ほど前、皆さんにアドバイスいただいて選んだ物件を去り、新しい部屋に引っ越しました。 おかげさまで非常に良い部屋に住むことができて大満足でした。

そこで今回は上記の記事を振り返って、実際にそのアドバイスが自分にとってどうであったかを振り返りつつ補足をしていきます。

当時の状況と、実際に住んでいた物件

  • 新卒社会人
  • 会社(目黒駅)から 2km 圏内だと家賃補助 2 万円が発生する
  • 家賃 : 10万3千円(当時)(家賃補助により実質負担8万3千円)
  • 最寄り駅 : 白金台
  • 占有面積 : 26.25m²
  • 間取り : 1K
  • 主要採光面 : 西
  • 築年月(築年数) : 2006年2月(築17年)
  • 建物構造 : ALC
  • 建物階建 : 地上3階

もらった知見

風呂トイレ

  • 風呂トイレ別にしろ
  • 風呂トイレ別とは別に、洗面台が独立してるのも大事です

別にしました。洗面所兼脱衣所に洗面台がある感じでした。特に困りませんでした。

  • 風呂場の換気扇がたまに電気と連動しているので絶対に確認したほうがいいです 冬に寒い
  • 浴室乾燥あると梅雨時期に便利

後述しますがドラム式洗濯機があったので乾燥が不要でした

共用施設

宅配ボックスのない物件でした。 自分の部屋の玄関前が廊下が膨らんでいるような形だったので、そこに置き配をしてもらっていましたので困ることはありませんでした。 構造上(玄関を塞がないか)、民度上(同じフロアの人と喧嘩にならないか・持ち去りされないか)、置き配が可能であれば宅配ボックスがなくても問題ないです。

  • オートロックはカス 締め出されるため.
  • オートロックの物件に住む時はマスターパスワードを聞いておきましょう

オートロックではない物件でした

  • 24時間ゴミ捨て可能ってのめっちゃ便利

それはマジでそう。 僕の物件は、24時間ゴミ捨て可能ではない物件でした。 朝起きる必要のない勤務体系ではあったので、朝起きてゴミを出すことが非常に苦痛でした。

キッチン

  • コンロは2口じゃないと死ぬ
  • 2口コンロは物置き

コンロが2口の物件でしたが、かなり助かりました。 ソースを作りながらパスタを茹でたり、味噌汁作りながら肉焼いたりできてよかったです。

  • 調子に乗ってでかい鍋とか買いまくるな 使うサイズを考えて

これは本当にそう。 ユーンさん に 鍋セット (これのセット9にシールリッドが含まれないやつ)を頂いて使っていました。 特に不便なく使えていました。ありがとうございます。

強いて言えばオタクを呼んで鍋を食べる時に若干大きいの買うか揺らぎがちですが、それで買うと後悔してたなと思います。

洗濯機

  • ドラム式洗濯機置ける部屋がいい
  • 室内洗濯機置場必須です
  • ドラム型洗濯機を買え。ヒートポンプ型がちと高いが良い。洗濯物を干す手間から完全解放される
  • まともな洗濯機買うと5万するし安い安い(ドラム式洗濯機について)

これを信じてドラム式洗濯機を買いました。本当に買ってよかった。干して畳むのは人類には難しい。 機種は これ でした。

  • はじめの物件は洗濯機置く場所がなくて辛かったので洗濯機置けると嬉しいと思います
  • あとドラム式洗濯機、幅もそうですが蛇口の高さも気をつけないとウチみたいになります

買う前に気にした方がいいですね。 うちはかなりギリギリ置けた感じでした。あと2センチ大きかったらアウトでした。 とはいえ購入前にきちんとメジャーで寸法を測って このようなガイド で測定した上でしたので安心して買えました。

これはマジです。僕の場合はワイシャツとチノパンがシワシワになってしまいます。 ドラム式洗濯機買う場合はセットでアイロン買った方がいいかもですね。

インターネット

  • VDSL物件なのに光物件と平気で謳っている物件に入ってしまった。光コンセントがあるかは確認しましょう
  • 光対応物件の中でも特に部屋まで光ファイバーが来てるところオススメです
  • 光ファイバーは最大速度が10倍くらい違うので
  • プロバイダ名+遅いとか、速度の実態を住む地域名で調べてみる

これは本当に怖かったので不動産業者と大家さん、またNTTに対してそれぞれ確認しました。 実際に入居してからも問題なかったです。 速度ですが、500mbps ぐらいは安定して出ていましたね。建物の総戸数が5戸だったのも大きいかなと思う。

建物に穴を開ける工事がNGだったのでやめておきました。 ですが、特にインターネットが遅くて困ることはなかったです。 Nuro 今大変そうですね。

  • 引越し業者とか不動産屋が勧めてくるやつはカカクコムとキャッシュバック比較して契約するといい
  • 安い光回線は下手するとLTEより遅いのに解約効かなくて死ぬので注意
  • NUROじゃなくてもIPoEは早いらしい

最初に SoftBank光 を契約していました。 携帯の回線が SoftBank だったためです。携帯回線とのセット割みたいなやつで安くなりました。 ちなみにサポートの評判が悪いのですが、そんなことは全く感じませんでした。

解約に関しては気にしていたので、カレンダーに解約金がかからないタイミングをメモしていました。 解約金がかからないタイミングで、ちょうど楽天ひかりに移行しました。 移行理由としては、楽天ひかりのキャンペーンで携帯回線(当時無料)とセットで契約すると1年タダとのことだったからです。

間取り

  • 稀に存在する20平米ない1kは狭いからつらそうという感じ
  • デカイ家具はちゃんと入るか確認したほうが幸せです

26.25m² ですが広くは感じませんでした。収納が全然足りなかったです。 家具に関しては最初の引っ越しだったので、まあ入るやつだけ実家からもらって行こうかなというノリだったのであまり気になりませんでした。

壁,構造

  • 上の部屋のセックスの声が聞こえる
  • 音が響くような壁は断熱材も殆ど入ってないから夏暑く冬クソ寒い部屋な事が多い
  • RC2階以上ってのはよく聞きますね 木造1階は騒音と虫が不人気なので
  • 壁が薄いと匂いと音が壁越えてやってくるので地獄
  • 匂いと音漏れはストレス溜まるからリモート勤務とかで家にいる時間長いなら防御力大事
  • 内見は他の部屋の人がいる時間を選んで壁を叩きまくって確認しよう
  • 軽量鉄骨は要注意
  • 近所の生活音はどうにもならないので、建屋のつくりについてはちゃんと見ておいた方がいいです

すごく優秀な防音断熱防臭でした。 内見する時に壁をコンコンして確認しました。

  • 1階の部屋に住む場合は、基礎がコンクリかどうかを確認しておいたほうがいいです。土むき出しの場合、湿気がすごくてカビが発生します

3階だったので気にしませんでした。 カビは問題なかったです。

ガス

  • 自分はプロパンガスの物件なのですが、都市ガスの友人宅と比べ、ひと月で数千円ガス代が高かったです
  • 都市ガス物件が選べるなら都市ガスのほうがいいですよ。LPガスとガス代が倍違うから。

都市ガスでした。 持論ですが、家賃が数千円増えてでも住みたい物件なら住む、といった考えでプロパンでも高くなることを意識して契約できればよさそうですね。

その他

  • 地上の駅近物件は始発から終電まで本当にずっと電車の音が鳴ってるので敏感なら気をつけてください
  • 電車は終電があるのでいいんだけど、高速道路が横にあると夜中も普通にうるさいので建物の防音がしっかりしててもつらいというのはあります。(慣れたので耳栓なしで夕方まで寝てられるけど)

大通りから少し離れた住宅街だったので大丈夫でした。 最寄りも地下鉄だったのでうるさくなかったです。

  • 予算的に無理すぎでは
  • 山手線内だと家賃10万円越えませんかこれ

家賃は当時(今は変わってました)だと10万3千円でした。 家賃補助により実質負担8万3千円だったのでまあ僕の収入的には大丈夫でした。

  • 掃除機はコードレスが良い。マキタのが安くてコンパクト。

ルンバを買いました。 床に物を置くことも防げていてよかったです。大金叩いて買った過去の自分を褒めてあげたい。 ルンバは1Kからもおすすめで、理由としてはルンバ様のために床に物を置かないように人間の習慣が矯正されるからです。 習慣を15万円(最低で4万から)で買えるのはコスパが良すぎです。

まとめ

後悔情報ベースで情報を募集していたので、罠を踏み抜く前に回避できたのはとても助かりました。 本当にありがとうございました。

ヴァーチャル美少女としてのZoom参加を支える技術

仕事で私との Zoom ミーティングをされる方は、私がヴァーチャルキャラクターの姿で通話に参加しているところを知っているはずです。 そこで私が初心者として、モデリングなどを通して各種ツールを用いた Zoom 通話への参加まで至った過程を簡単に説明していこうと思います。 この記事が、みなさんが自分の望む様々な姿で通話に参加できるようなきっかけになれば嬉しいです。

この記事は、 Akatsuki Advent Calendar 2021 の10日目の記事です。

続きを読む