Google Apps Script の拡張サービスの TypeScript 用型定義ファイルの自動生成

こんにちは、メディアプロダクト開発部の後藤(id:mtgto)です。

今回は Google Apps Script の28個の拡張サービスについて、 TypeScript 用の型定義ファイル (@types/google-apps-script) を、Web エディタのオートコンプリートマクロ用のデータから自動生成するプログラムを作成した話を紹介します。

Google Apps Script の紹介

読者の皆様はGoogle Apps Scriptはご存知でしょうか。名前は聞いたことがあるけど使ったことはあまりないという方が多いでしょうか。

Google Apps Script を使うことでドキュメント、スプレッドシート、スライド、フォームといった Google サービスのデータの取得・更新などを ECMAScript のプログラムから行うことができます。 例えば、

  • スプレッドシートのセルを読み込んでドキュメントに出力
  • Drive のファイル一覧を読み込んでスプレッドシートにリンク付きで出力
  • 外部 API にアクセスするスクリプトを一定周期ごとに動かす

このようなことを無料で行うことができます1

クックパッドでは Google Workspace をオフィススイートとして広く使用しており、スプレッドシートやドキュメントの自動化・マクロ実装などに Google Apps Script が利用されています。

Google Apps Scriptを普段の作業の改善に使うことも可能です。たとえば定例のテンプレートを google docs で管理してておき、次回の定例前にApps Scriptで雛形を追加する、といったこともECMAScriptを少し書くことで可能です。

私は以前Google Apps Scriptを利用して、期間と会議室の候補を入れると参加者の予定から空き時間&空き会議室を探してくれるウェブアプリを作りました。

f:id:mtgto:20210308214802p:plain
会議時間、参加者、会議室から、全員の空き時間と空き会議室を検索

f:id:mtgto:20210308215018p:plain
検索結果をクリックし、タイトルと説明を埋めて「登録」を押すとGoogle Calendarに予定が作られます

英語版のみですが、Codelabs に概要を知るためのチュートリアルも用意されています。 ざっくり Google Apps Script でできることの概要を知りたい方はちょうどいいかもしれません。

https://developers.google.com/apps-script/quickstart/fundamentals-codelabs?hl=ja#getting_started

Google Apps ScriptをTypeScriptで開発したい

Google Apps Scriptは前述の通り、ECMAScriptで記述する必要があります。 Webエディタも用意されていますが、ユニットテストやトランスパイルを必要とする場合、gitバージョン管理をしたいなどの場合にはGoogle謹製の clasp を使うことでCLIからの開発が実現できます。

clasp + TypeScriptでGoogle Apps Scriptのプログラムを書く場合には、@types/google-apps-script npmパッケージを使うことでGoogle Apps ScriptのAPI定義を利用できます。

標準サービス (Built-in services) と拡張サービス (Advanced services)

Google Apps Script で利用できる Google のサービスには標準サービス (Built-in services) と拡張サービス (Advanced services) という分別があります。

標準サービスのうちでもCalendar, Slides, Driveなど様々なGoogleサービスを利用できますが、さらに拡張サービスを使うことでGoogleの公開APIをECMAScriptから利用することが可能になります。

拡張サービスじゃないとできないことの例としては、

  • 自分以外のメンバーのカレンダーの予定を取得
  • 組織内のメンバーや会議室の情報を取得 (Admin Directory)
  • YouTube、Analytics、BigQuery などの標準サービスの対象外サービスの公開APIの利用

などがあります。

先程紹介したメンバーと会議室の空き時間を調整するアプリでは自分以外のメンバーの予定を調べないとだめなので、拡張サービスの利用がどうしても必要でした。

標準サービス用のTypeScriptの型定義は id:motemen によってAPIリファレンスから自動生成することで用意されていたのですが、拡張サービスの型定義は残念ながら用意されていませんでした。

そこで私が拡張サービスの型定義の自動生成に挑戦することにしました。

https://github.com/mtgto/dts-google-apps-script-advanced

Webエディタの自動補完定義からの自動生成

Google Apps Scriptで利用できる拡張サービスの数は2021年現在 28、利用できる関数の数は 2913 にもなります。

拡張サービスは標準サービスと違って API ドキュメントも公開されていないためスクレイピングによるAPI定義の取得もできません。 そこでWebエディタの自動補完マクロ用に用意されているJSONPデータに注目しました。

Webエディタでは拡張サービスであっても関数一覧が自動補完されます。

f:id:mtgto:20210308215049p:plain
GAS のエディタでの自動補完

自動補完の実現のために拡張サービスごとの関数やクラス構成はJSONPをWebエディタが読み込むことで実現しているようでした。例えば拡張サービスのCalendarの自動補完JSONPですと、

{
  "1": {
    "1": "Calendar_v3",
    "2": [
      {
        "1": "Acl",
        "2": "Calendar_v3.Calendar.V3.Collection.AclCollection",
        "3": 1
      },
      {
        "1": "CalendarList",
        "2": "Calendar_v3.Calendar.V3.Collection.CalendarListCollection",
        "3": 1
      },
      {
        "1": "Calendars",
        "2": "Calendar_v3.Calendar.V3.Collection.CalendarsCollection",
        "3": 1
      },
      ...
      {
        "1": "Events",
        "2": "Calendar_v3.Calendar.V3.Collection.EventsCollection",
        "3": 1
      }
    ],
    "3": [
      {
        "1": "newAclRule",
        "2": "Calendar_v3.Calendar.V3.Schema.AclRule",
        "6": "Create a new instance of AclRule"
      },
      ...

このようなデータが用意されています。2 このJSONをダウンロードし、プログラム内部でTypeScriptのnamespaceとinterfaceの構造に変換し、最後にd.tsファイル形式で出力することができるようにしました。

namespace Calendar {
    namespace Collection {
        interface AclCollection {
            // Returns an access control rule.
            get(calendarId: string, ruleId: string): Schema.AclRule;
            // Returns an access control rule.
            get(calendarId: string, ruleId: string, optionalArgs: object, headers: object): Schema.AclRule;
        }
    }
}

Google Apps Scriptの中がJavaScriptではないなにかで書かれているためか、基本データ型として number の代わりに Integer となっていたりするのでそのような型の変換を行うなどしてTypeScriptの型定義として忠実に再現しています。残念ながらJSONデータの中で名前しか公開されてないデータ構造を関数のレスポンスの型として使っていたりするので、そこは諦めて any で定義しています。

私が最初に型定義ファイルの自動生成プログラムを書いたときには自分が利用したいCalendarとAdmin Directoryにだけ対応していればよかったので、その2つだけDefinitelyTypedにPull Requestを出してマージしてもらいました。 そのあとGoogle Apps Scriptの中の人と話したりしてなんやかんやあり、結局全ての拡張サービスに対応できるようにプログラムの修正をしました。

まとめ

Google Apps Scriptの拡張サービスのTypeScriptの型定義をWebエディタの自動補完用のデータから自動生成するプログラムを書いた話を紹介しました。

Google Workspaceをがっつり利用している会社や組織の場合、Google Apps Scriptの標準サービスだけでも色々なことができることはあると思いますが、さらに拡張サービスを使うことで実はこんなこともできる、みたいな展開があるかもしれません。 また小さいスクリプトをGoogle Apps ScriptのWebエディタから書くのもいいですが、clasp + @types/google-apps-scriptを使うことでCLIでのTypeScriptでの開発もしやすくなっています。

QiitaのGoogle Apps Scriptタグ にもたくさん記事が投稿されていますので興味のある方はご覧ください。

クックパッドでは仲間を募集しています

クックパッドではモダンなフロントエンドの基盤作りレガシーなフロントエンドからの移行を始め、web開発に力を入れています。もしこの記事を見て興味がありましたらお気軽にお問い合わせください。

https://info.cookpad.com/careers/


  1. 注意:無料なのはQuota の範囲内の場合です。

  2. Googleアカウントでログインしてないとダウンロードできないため、28サービス分のJSONPのダウンロードを手動でやる羽目になって地味に面倒でした。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/